Introduction
Have you ever had to do something like this:
string typeName = "MyDataType";
Type type = Type.GetType( typeName );
object o = someGenericClass<typeof(type)>();
If you have, then you have also discovered that this does not compile. The good news is this capability is possible in C#, but it's not obvious. This article is aimed at unmuddying the waters surrounding reflection and generic
types. We will show how to accomplish this with a generic
class (and, as an added bonus, the equivalent functionality for generic
methods too).
Background
Why would you ever need to do something like this? I ran into this problem when creating an application framework that utilized dynamically loaded .NET Assemblies with well defined interfaces. Some of those interfaces were generic
, while others had generic
methods in them.
The problem occurred in implementing the framework, where the data types were not known at compile time, but were dynamically discovered through Type
objects. It was not possible to instantiate the dynamically loaded generic
types using dynamically loaded data types in any obvious way.
After much searching on the Internet and a lot of spelunking on MSDN, I finally discovered the secret sauce to using Generics with reflection.
Types and Generics
The key to all of this is in understanding the two different Type
instances that can be associated with a Generic
class. When you define a Generic
class, you are leaving one or more member types undefined until it is used in code somewhere. This is known as an "open" generic. When you declare a reference to one of these Generic
classes in code and you provide the actual type(s), you have "completed" the definition of the class. This is known as a "closed" generic.
For all of our examples, we will be using the following Generic
class definition:
namespace ReflectGenerics
{
public class GenericClass<T>
{
public GenericClass( T t )
{
_t = t;
Console.WriteLine( "GenericClass<{0}>( {1} ) created",
typeof( T ).FullName, _t.ToString() );
}
private T _t = default( T );
public T GetValue()
{
Console.WriteLine( "GetValue() invoked, returning {0}", _t.ToString() );
return _t;
}
public static U StaticGetValue<U>( U u )
{
Console.WriteLine( "StaticGetValue<{0}>( {1} ) invoked",
typeof( U ).FullName, u.ToString() );
return u;
}
}
}
Let's look at some code examples:
GenericClass<int> t = new GenericClass<int>( 1 );
string typeName = "ReflectGenerics.GenericClass";
Type openGenericClass = Type.GetType( typeName );
Type closedGenericClass = typeof( GenericClass<int> );
If we step through this code in a debugger and inspect the Type
objects, we can see some differences between the openGenericClass
and the closedGenericClass
. The Type
class has some properties that can help us determine what state our Generic
class is in. The relevant properties are: IsGenericType
, and IsGenericTypeDefinition
. Each of these return bool
.
If we inspect openGenericClass
, we see that IsGenericType
is true
, and IsGenericTypeDefinition
is true
. If we inspect closedGenericClass
, we see that IsGenericType
is true
, and IsGenericTypeDefinition
is false
. What we can glean from this is that if a class is a Generic
class, then its associated Type
object will always return true
to IsGenericType
. However, if the Type
represents a Generic
class that has its type argument(s) undefined, then IsGenericTypeDefinition
will also return true
.
To create an instance of an object from only its Type
definition, we can use the Activator
class. However, to instantiate a Generic
type, we need to have a closed Type
(i.e. - one where all of its generic type arguments have been defined). Okay! This is good. But what if we have a Type
object for the generic type arguments and an open generic Type
? How do we turn those into a closed generic Type
?
There is a helper method in the Type
class called MakeGenericType
that turns an open type into a closed one.
Type dynamicClosedGenericClass = openGenericClass.MakeGenericType( typeof( int ) );
Now that we've taken an open type and added the necessary generic type arguments to it, we can use this closed type to instantiate an object.
object o = Activator.CreateInstance( dynamicClosedGenericClass, 1 );
object rv = dynamicClosedGenericClass.InvokeMember
( "GetValue", BindingFlags.InvokeMethod, null, o, new object[ 0 ] );
Console.WriteLine( "GetValue() returned {0}", rv.ToString() );
Note that this code is functionally equivalent to:
GenericClass<int> t = new GenericClass<int>( 1 );
Console.WriteLine( "GetValue() returned {0}", t.GetValue() );
Generic Methods
The only thing left for us to figure out are generic methods. Our sample generic
class has a static generic
method defined for us to use as an example. Everything we learned about open generic
types and closed generic
types for classes is also true
for methods except that we need to use the MethodInfo
class instead of the Type
class.
GenericClass<int>.StaticGetValue( 3 );
MethodInfo openGenericMethod =
typeof( GenericClass<> ).GetMethod( "StaticGetValue" );
MethodInfo closedGenericMethod =
openGenericMethod.MakeGenericMethod( typeof( int ) );
object o2 = closedGenericMethod.Invoke( null, new object[] { 4 } );
Console.WriteLine( "o2 = {0}", o2.ToString() );
MethodInfo
has the properties IsGenericMethod
and IsGenericMethodDefinition
. These are exactly analogous to the two properties in the Type
class we were inspecting. A MethodInfo
can likewise be "open" or "closed" if it refers to a generic method. Finally, turning an open MethodInfo
into a closed MethodInfo
is done with the MakeGenericMethod
method.
Summary
So, we have learned how to take a Type
instance and inspect it to see if it is generic
, and if it is open or closed. If it is open, we learned how to use the MakeGenericType
method to close it, so we can instantiate it with the Activator
. We also learned how to perform the equivalent actions on a generic method, using the MakeGenericMethod
method on the MethodInfo
class to close an open MethodInfo
.
History
- 12th December, 2007: Initial version