This is a follow-up to this question. Only bother reading it if you’re interested in the back-story.
In short, I am using reflection to load a set of generic types from an assembly. The assembly is loaded at run time.
I have a second assembly referenced both by my current project and the assembly I’m loading which contains the interfaces:
IJobIJobWrapper(Of IJob)
The Assembly I’m loading (Call it Jobs.dll) contains a JobWrapper(Of IJob) – which implements the IJobWrapper interface.
Jobs.dll also contains multiple jobs which implement IJob
Now, I’m loading the appropriate Types into a container (Unity) and pulling them out when required. This works (that is to say, the container resolves the references as appropriate and instantiates an object)
NB: JobType and JobWrapperType below are retrieved via reflection.
Specifically:
Dim TypeArgs As Type() = {JobType}
Dim WrappedJob = JobWrapperType.MakeGenericType(TypeArgs)
Dim ContainerJob = Container.Resolve(WrappedJob)
Dim JobInstance = DirectCast(ContainerJob, IJobWrapper(Of IJob))
What’s throwing an error here is the last line with the cast.
ContainerJob on line 3 is technically an object as I need to use the non-generics overload to do the resolution (ie a Type parameter not an ‘(Of XXX)’).
According to the debugger, however it’s actually a MyProject.Jobs.JobWrapper(Of MyProject.Jobs.DailyStatusReport)
MyProject.Jobs.JobWrapper Implements IJobWrapper(Of IJob)
MyProject.Jobs.DailyStatusReport Implements IJob
The DirectCast throws this exception:
Unable to cast object of type 'MyProject.Jobs.JobWrapper`1[MyProject.Jobs.DailyStatusReport]' to type 'MyProject.JobService.Common.IJobWrapper`1[MyProject.JobService.Common.IJob]'.
Can someone please explain why it can’t do the cast / how to get around it? I’m wondering if it is having trouble matching the Interface defined in the referenced assembly with the one referenced from within Jobs.dll – But if that is the case, I’m unsure how to reconcile the two.
Many thanks.
EDIT:
Sample methods from the interfaces:
IJob:
Function ShouldExectute() As Boolean
Sub Execute()
IJobWrapper:
Function ShouldExectute() As Boolean
Sub Execute()
ReadOnly Property DatabaseId as Long
ReadOnly Property Name as String
ReadOnly Property IsDisabled As Boolean
Eg – The Job Wrapper doesn’t ever return anythign with the type of the IJob. It does use the type information to look up various Attributes which have been attached to the class which implements IJob and read information.
It is not possible to perform this cast (with or without reflection) because generic type parameters are required to match exactly. So you could cast an
AnyImplementationOfIJobWrapper(Of IJob)toIJobWrapper(Of IJob), but you can not cast anAnyImplementationOfIJobWrapper(Of AnyImplementationOfIJob)toIJobWrapper(Of IJob).However, if you control the code for
IJobWrapper, you can turn it into a covariant interface by declaring it asInterface IJobWrapper(Of Out IJob). This will allow you to perform the cast. It is the same mechanism that allows you to e.g. assign anIEnumerable(Of Subclass)toIEnumerable(Of Superclass).(Feel free to correct any syntax mistakes I might have made; it’s been a while since I used VB.)
A natural question would be “Why is not this cast allowed in the first place?” The reason is that it would lead to dangerous situations: Assume that you have an
IList(Of Vehicle) vehicles. It seems natural that it should be possible to assign aList(Of Car)tovehicles, since a list of cars could be considered a list of vehicles. But if we allowvehiclesto refer to what is truly aList(Of Car), we could try to add aBoattovehicles, which should be allowed sincevehiclesappears to be a list ofVehicles and aBoatis aVehicle. However, the actual list is restricted to only containCars, so we must either throw an exception (very unexpected for the user ofvehicles) or insert theBoatinto a list ofCars (creating a disaster waiting to happen). Solution: disallow the cast.IEnumerable(but notIList) can be made covariant because it only contains methods that lets you read from the collection. Therefore, anIEnumerable(Of Car)can be assigned to anIEnumerable(Of Vehicle)becauseIEnumerableonly lets you read contents of the collection, not insert new objects into it.