Click here to Skip to main content
Click here to Skip to main content

Reflecting on Generics

, 12 Dec 2007
Rate this:
Please Sign up or sign in to vote.
How to access, use and interpret generic classes and methods using reflection

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:

// create an instance of a generic class 
GenericClass<int> t = new GenericClass<int>( 1 );

// get the type of the generic class definition using just the class name
string typeName = "ReflectGenerics.GenericClass";
Type openGenericClass = Type.GetType( typeName );

// get the type of the generic class with the generic parameter defined
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.

// get the type of the generic class from the class definition type
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.

// invoke the static template method directly
GenericClass<int>.StaticGetValue( 3 );

// get the open generic method type
MethodInfo openGenericMethod = 
    typeof( GenericClass<> ).GetMethod( "StaticGetValue" );

// get the close generic method type, by supplying the generic parameter type
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Derek Viljoen
Software Developer (Senior)
United States United States
Developer with over twenty years of coding for profit, and innumerable years before that of doing it at a loss.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberTushar_Patil13-Mar-12 2:37 
GeneralPerfect timing PinmemberSteven Berkovitz17-Dec-07 15:20 
GeneralNice article! Pinmembermrchief_200013-Dec-07 7:30 
I remember coming across the exact situation once. That was not a production code rather my R&D so I didn't look further into it and concluded that it is not posible.
 
But now that I have insight as to how it can be done, I sure have another option to consider while writing my next piece of earth-shattering code! Wink | ;)
GeneralThanks! Pinmembercyfyguy13-Dec-07 4:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 12 Dec 2007
Article Copyright 2007 by Derek Viljoen
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid