Friday, March 9, 2012

Reflection: working with generic types

An interesting problem I was facing during last few days drives me to write this post. The problem was to create a generic type instance (let's say List), the type parameter for which is known only during runtime. Let's examine the following code fragment:
Type genericListOfInt = typeof(List<int>);
Type genericListOfString = typeof(List<string>);
bool sameType = genericListOfInt == genericListOfString;
//sameType is false here, so those Types are differenet
Both the variables seems to be instances of the same List (List of T) type, but because the T type parameter is different the compiler will generate two different classes for List and List so the variables will not be equal. But how to handle the problem we do face ?
Examining the System.Type class we will find out a very interesting method called:GetGenericTypeDefinition() method. This method returns a Type object, representing the generic type definition, from which the type, which for the method was called, was constructed, so actually the type, which can be used to construct other Generic types as well.
So modifying the code above will give us the following:
Type genericListOfInt = typeof(List<int>);
Type genericListOfString = typeof(List<string>);

bool sameType = genericListOfInt == genericListOfString;
//sameType variable is true now, so both variables are referencing the same Type instance
Apparently, the type, which is reffered by both genericListOfInt and genericListOfString variables can be accessed with the following variable as well:
Type genericList = typeof(List<>);
This makes it really handy, so we don't want the compiler to generate any classed which are actually useless just to get the reference to a generic type.
Now this type can be used to construct different Lists for different types dynamically (List<[any type you want here]>). So here is a simple code for constructing a list of the given type:
public static object CreateGenericList(Type ofType)
  Type genericListType = typeof(List<>);
  return Activator.CreateInstance(genericListType.MakeGenericType(new Type[]{ofType}));
Please note also, that the List type was taken for example only, so to access multi-type based generic type (like KeyValuePair<k,v>) you can easily use the same technique:
Type genericKVPair = typeof(KeyValuePair<,>);
Now, the last scenario left to discuss, is how to dynamically identify the generic type parameters a type has (string in case of List and (int, string) in case of KeyValuePair)? The Type class defines another interesting method called GetGenericArguments(), which returns an Array of Type objects. Combining this method with MakeGenericType will let us write the following code, which is used to build a KeyValuePair of Types, which are the switch from the original one by type parameter places (for KeyValuePair it will return KeyValuePair):
public object CreateInstnaceOfTheSameType(object original)
  if (original == null)
    return null;

  Type origType = original.GetType();
  if (!origType.IsGenericType || origType.GetGenericTypeDefinition() != typeof(KeyValuePair<,>)
    throw new ArgumentException("KeyValuePairs expected");

  Type[] typeArgs = origType.GetGenericArguments();
  Type typeToConstruct = origType.GetGenericTypeDefinition().MakeGenericType(new Type[] { typeArgs[1], typeArgs[0]});
  object[] constructorParams;
  // I'm skipping the code for constructorParams initialization, cause it's out of scope
  return Activator.CreateInstance(typeToConstruct, constructorParams);
Hope you will find this post helpful.

Post a Comment