Welcome to CrankyGoblin.Com Sign in | Join | Help

Public Class GeoffAppleby

Inherits Microsoft.VisualBasic.MVP : Implements IBrainFart
Reflection and Generics

Reflection is one of my favourite areas in the entire .net framework. Reflection certainly has it's down points, most particularly in the speed area, but at times it's a cost that's acceptable given the power you get.

And to be honest, while 'reflection is slow' has become a bit of an axiom in the last 4 years, that's only speaking comparatively - realistically it's still pretty fast, it's just slower compared to a hard coded way (of course).

But it's fun area to play in.

Recently I discovered that I had the need to instantiate an instance of a generic class. This really brings things to a whole new level! Generics is a new feature in the 2.0 version of the .net framework. It's sort of like templates in ATL/STL (at least at first glance) but it's so much more. And just like reflection, the more you play with it, the more you can do, the more you learn, the more you play, etc :)

Let's say we want to auto-instantiate some given type that was provided to us from some unknown place. We can use the Activator to instantiate it for us:

        Dim oType As System.Type

        Dim oHashtable As Hashtable

 

        oType = GetType(Hashtable)

        oHashtable = DirectCast(System.Activator.CreateInstance(oType), Hashtable)

This is obviously a very simple case, but it all comes down to ensuring we get a valid System.Type object. After that, the rest is easy. To get the valid Type object, you have a few mechanisms available - querying an assembly, providing a string that represents the Type.FullName, and others besides.

Now let's try this on a generic class:

    Public Class Fred(Of T)

 

        Private mMemberVar As T

 

        Public Sub New(ByVal someParam As T)

            mMemberVar = someParam

        End Sub

 

    End Class

 

    Private Sub Fnord()

 

        Dim oType As System.Type

        Dim oFred As New Fred(Of String)("foo")

 

        oType = oFred.GetType()

        oFred = DirectCast(System.Activator.CreateInstance(oType), Fred(Of String))

 

    End Sub

This is obviously a bit contrived, as we don't need to use reflection in this specific code - we obviously already know the hard coding that we want Fred(Of String). But I wanted to give you code that would compile and run successfully.

This is all well and good, but everything about the generic class was known well before (all the 'Of String's that are there. But if you don't know what the generic parameters are, what can you do?

Well for starters let's setup a new contrived circumstance that has the appearance of being innocent.

    Public Class Fred(Of T)

 

        Private mMemberVar As T

 

        Public Sub New()

            mMemberVar = Nothing

        End Sub

 

        Public Class Barney

 

            Private mOtherMemberVar As T

 

            Public Sub New()

                mOtherMemberVar = Nothing

            End Sub

 

        End Class

    End Class

 

    Private Sub Fnord()

 

        Dim oType As System.Type

        Dim oFred As New Fred(Of String)

        Dim oBarney As Object

 

        oType = oFred.GetType.GetNestedType("Barney")

        oBarney = System.Activator.CreateInstance(oType)

 

    End Sub

So what happens when this is run? We get an ArgumentException in the call to CreateInstance, with a message of  'Cannot create an instance of Cobs.TestHarness.Form1+Fred`1+Barney[T] because Type.ContainsGenericParameters is true.'.

It was when I hit this exact problem (in a more complex but effectively the same code base) that I learned I had a lot of research to do :)

The first things I read up on was this Type.ContainsGenericParameters thing. This is a new property in the 2.0 framework. If this property returns true, then the type itself contains parameters for a generic type, but it hasn't been given any specific type information to replace those parameters yet.

This confused me. The definition of the Barney type came directly from a type that represented Fred(Of String), didn't it? Well, yes it did - but nested classes are only positional - The definition of Fred(Of Anything!) does not itself know anything about Barney, only that Barney is living inside him like some sort of alien chest-burster.

On top of this, at first glance I thought that Barney didn't have any generic parameters - while he referenced T, he didn't have any (Of ...) statements in him. Well, that's where I was wrong yet again - Barney inherits his parent classes generic parameters, even though we don't show them explicitly. Here's the IL to prove it:

.class nested public auto ansi Barney<T>
  extends object
{
  .method public specialname rtspecialname instance void .ctor() cil managed
  {
  }
 
  .field private !T mOtherMemberVar
}

Barney gets a <T>, (which in VB equates to (Of T)) just like Fred did. So Barney is some generic class that just has a T, and System.Activator has no way of knowing what to fill in for those bits.

After a lot of playing and hunting and getting nowhere I was about ready to give up on the whole thing - until I mentioned to Bill that it couldn't be done. He said it could - which is all I needed to keep spurring me on :) Within the next 5 minutes I'd stumbled across what I needed, and was good to go again.

On a type where the ContainsGenericParameters is True, you need to call the Type.MakeGenericType method to get a type definition where all the parameters have been filled in with real substantive types. This method takes an array full of types to replace the generic parameters - the end result is a type that returns False on a call to Type.ContainsGenericParameters, and so therefore System.Activator can use it.

If you ask me, Type.MakeGenericType is a dodgy name for a method. We're not making a generic type, we're making a string type based on a generic type, aren't we? Oh well.

So how do we make ourselves a Barney now? Sure, I can fill in this array with a type for T, but again I've got to hard code this bit don't I? Time to get reading again - Type.GetGenericArguments returns an array of the Type parameters used to construct the generic type in question.

And so now, armed with an instance of a Fred, we can very easily make a matching Barney, even if we don't know how Fred came to be.

    Public Class Fred(Of T)

 

        Private mMemberVar As T

 

        Public Sub New()

            mMemberVar = Nothing

        End Sub

 

        Public Class Barney

 

            Private mOtherMemberVar As T

 

            Public Sub New()

                mOtherMemberVar = Nothing

            End Sub

 

        End Class

    End Class

 

    Private Sub Fnord()

 

        Dim oType As System.Type

        Dim oFred As New Fred(Of String)

        Dim oBarney As Object

 

        oType = oFred.GetType.GetNestedType("Barney").MakeGenericType(oFred.GetType.GetGenericArguments())

        oBarney = System.Activator.CreateInstance(oType)

 

    End Sub

You wouldn't normally chain the method calls together like I did (GetNestedType to MakeGenericType to GetGenericArguments), but it shows the code you need - and again, if you run this code, it will work :)

There's a lot more to all this than what I've just described here - the learning and research goes ever on (which is good :) looking at things like Type.IsGenericParameter, Type.IsGenericType, Type.IsGenericTypeDefinition and many more, but I think this is a pretty nice intro to the whole thing so far.

Posted: Tuesday, April 04, 2006 10:31 PM by Geoff Appleby
Filed under: ,

Comments

Duncan Bayne said:

Thanks - I'm having exactly the same problem.  Now, to print out this post and go and read it, slowly, until it sinks in :-)

# August 1, 2007 4:26 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

To submit your comment, click on these pictures:
  • Shocked Geoff
  • Geoff's dad's tongue
  • Angry Geoff
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Can't recognise the people in these pictures? Look here for a quick introduction.
There's a time limit for you to get your comment submitted before this set of pictures expires. If you think it's been longer than 10 minutes, get some new pictures first (you won't lose what you've typed so far).
Get some new pictures 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS