Reflecting Generic Type Information
The fifteenth part of the Reflection tutorial describes reflection techniques for obtaining information about generic types. The article looks at some of the Type members introduced in the .NET framework version 2.0 that are specific to generic types.
Microsoft introduced generics in version 2.0 of the .NET framework. Generics allow you to create highly reusable classes, structures and interfaces without fixing the actual data types until those classes are instantiated. The types are defined with type parameters, which are replaced with real types when objects are created. For example, the base class library includes a generic list, named "List<T>". "T" is the generic type parameter that is replaced with a real type on instantiation, for example, becoming List<int> for a type-safe list of integer values.
The introduction of generics adds some terminology that is essential for this article. We will use the following important definitions:
- Generic Type. A generic type is any interface, class or structure that includes generic type parameters.
- Generic Type Definition. A generic type definition is a generic type where none of the type parameters have been set to fixed types. For example, List<T> is a generic type definition because the T parameter has not been assigned. List<string> is not a generic type definition.
- Open Constructed Type. An open constructed type has one or more unassigned type parameters. It differs from a generic type definition in that it has a mixture of assigned and unassigned type parameters.
- Closed Constructed Type. A closed constructed type is a generic type that has only assigned type parameters.
- Generic Type Parameter. A generic type parameter is an unassigned type parameter within a generic type definition. It can be thought of as a placeholder in a template that can later be used to construct closed types.
- Generic Type Argument. A generic type argument is an assigned or unassigned type parameter in a generic type. In List<T>, the generic type argument is the generic type parameter, "T". In the closed type, List<string>, the generic type argument is "string".
When you use reflection to retrieve the details of a generic class, structure or interface, there is additional information to be assembled than when reflecting non-generic types. Specifically, the type arguments for the generic type change the way the type behaves and understanding this when performing some reflection functions is essential. For example, generic type definitions can be used to create closed constructed types but the method used to do this cannot be used against a constructed type without causing an exception.
In this article we'll look at how to determine whether a type is generic and whether a generic type is a type definition, an open constructed type or a closed constructed type. We'll also extract the details of type parameters from a type, describe how a constructed type can be created from the template of a generic type definition and obtain a generic type definition from a closed type. The example code uses standard types from the .NET framework but you can use the same techniques for your own classes too.
Checking if a Type is Generic
You can obtain a Type object for a generic type in the same manner as for any other type. This means that there are situations where you create a Type instance without knowing beforehand if the type it describes is generic. As some reflection techniques do not work with non-generic types, you can check the Type object's IsGenericType property to make this determination. If the underlying type is generic, the property returns true.
You can see this in the following example code:
isGeneric = typeof(KeyValuePair<int, string>).IsGenericType; // true
isGeneric = typeof(string).IsGenericType; // false
Checking if a Generic Type has Open Type Parameters
Types that have generic type parameters that have no assigned types cannot be instantiated. To check if the type that you have reflected has such unassigned parameters, you can check its ContainsGenericParameters property. If any of the type parameters are unassigned, even if others have fixed types, the property returns true. If the reflected item is a closed constructed type, the property is false, as shown below:
hasOpen = typeof(KeyValuePair<,>).ContainsGenericParameters; // true
hasOpen = typeof(KeyValuePair<int, string>).ContainsGenericParameters; // false
Checking if a Type is a Generic Type Definition
A third flag property exists that allows you to determine if a type instance represents a generic type definition, meaning that all of its type arguments are unassigned. This is the IsGenericTypeDefinition property and is demonstrated in the following code sample:
isGTD = typeof(KeyValuePair<,>).IsGenericTypeDefinition; // true
isGTD = typeof(KeyValuePair<int, string>).IsGenericTypeDefinition; // false
By combining the three flags you can determine the category of type the reflected item belongs to. The following table shows the possible flag combinations for non-generic types, generic type definitions, open constructed types and closed constructed types:
|Generic Type Definition||true||true||true|
|Open Constructed Type||true||true||false|
|Closed Constructed Type||true||false||false|
16 June 2012