Generics Terminology in .NET Framework

Hi Folks...

The other day I was investigating an issue that finally boiled down to incorrect usage of System.Reflection & System.Reflection.Emit APIs (SR & SRE hereafter) to analyze Generic types. We were using Type.IsGenericParameter instead of using Type.IsGenericType. During investigation, what made things worse is that each time I went through the first paragraph of their respective help pages, I got more confused. Then I decided to read the Remarks section of the help pages and I was hopelessly lost because it seemed to be written in a dialect of English that I didn’t speak! 

Do you know the difference between Type.IsGenericParameter and Type.IsGenericType? The difference between Generic Type Parameter and Generic Type Argument perhaps? How about Closed Constructed Generic Method and Open Constructed Generic Method? These are a part of some subtle terminology introduced into .NET Framework with Generics. Using SR & SRE without a clear understanding this terminology is only going to land you into trouble. You are going to introduce bugs into your code and you will have a really hard time debugging your own code! Just like me!

It is all there in MSDN but it is highly confusing at the first glance! So I decided to get done with this for good. I spent a good 3 hours going over Generics terminology in .NET Framework in MSDN. This is a gist of my understanding. I am all clear now. You should be too before you begin using SR and SRE.

Basic Terminology

  • Generics:
    • They are classes/structs/interfaces/methods (CSIM hereafter) that have placeholders for one or more of the types they store and/or use.
  • Generic Type:
    • Union of the set of all Generic Type Definitions and Constructed Types.
    • A System.Type object represents a Generic Type iff its IsGenericType property is true
  • Generic Method:
    • Must have a non-empty list of Generic Type Parameters.
    • A method being a Generic Method doesn’t have anything to do with the enclosing type being Generic Type or not
    • Union of the set of all Generic Method Definitions and Constructed Methods.
    • Generic Type Parameters can appear as the return type or as the types of the formal parameters
    • A System.Reflection.MethodInfo object represents a Generic Method iff its IsGenericMethod property is true
  • Generic Type Definition:
    • A CSI declaration that serves as a template, with placeholders for the types it can contain or use.
    • A System.Type object represents a Generic Type Definition iff its IsGenericTypeDefinition property is true
  • Generic Method Definition:
    • A method with two parameter lists: A non-empty list of placeholder types and a list of formal parameters
    • A System.Reflection.MethodInfo object represents a Generic Method Definition iff its IsGenericMethodDefinition property is true
  • Generic Type Parameter:
    • It is the placeholder type in a Generic Type/Method Definition.
    • It is represented using a System.Type object in SR and SRE.
    • A System.Type object represents a Generic Type Parameter iff its IsGenericParameter property is true
    • Example: Is T in Dictionary<T, int> in "class C<T> : Dictionary<T, int>" a Generic Type Parameter? Yes!
  • Generic Type Argument:
    • It is any type that is substituted for a Generic Type Parameter. It could be another Generic Type Parameter.
    • IsGenericParameter is false
    • A System.Type object represents a Generic Type Argument iff its IsGenericParameter property is false
  • Constructed Generic Type/Method:
    • A new type/method constructed as a result of specifying actual types for one or more of the Generic Type Parameters of a Generic Type/Method Definition.
    • Example: Is A<B<, >, > a Constructed Generic Type? Yes!
  • Closed/Open Constructed Generic Type:
    • A Closed Constructed Generic Type is the result of specifying Generic Type Arguments for all Generic Type Parameters of a Generic Type/Method Definition. An Open Constructed Generic Type otherwise.
    • A System.Type object represents a Closed Constructed Generic Type iff its ContainsGenericParameters property is false. Otherwise it is an Open Constructed Generic Type.
    • Only a Closed Constructed Generic Type can be instantiated
    • Is A<B<T, string>, int>.C a Closed Constructed Generic Type? No!
  • Closed/Open Constructed Generic Method:
    • A Closed Constructed Generic Method has no unassigned Generic Type Parameters *and* the containing type is a Closed Constructed Generic Type *and* all the Generic Type Arguments are Closed Constructed Generic Types. An Open Constructed Generic Method otherwise.
    • A System.Reflection.MethodInfo object represents a Closed Constructed Generic Method iff its ContainsGenericParameters property is false. Otherwise it is an Open Constructed Generic Method
  • Constraints:
    • They are limits placed on Generic Type Parameters.
    • The GenericParameterAttributes property of a System.Type object representing a Generic Type Parameter gets a combination of System.Reflection.GenericParameterAttributes flags that describe its covariance and special constraints
    • The GetGenericParameterConstraints() method of a System.Type object representing a Generic Type Parameter returns an array of System.Type objects that represent its constraints.

A few points to note

  • If a nested types doesn't have Generic Type Parameters of its own, the enclosing type determines the above for it.
  • Generic Type Parameters and Generic Type Arguments have the same relation as the parameters and arguments of a function.
  • typeof(Dictionary<, >).MakeArrayType() returns a System.Type object that is not a Generic Type but is an Open Constructed Generic Type. Same for pointers
  • In SR & SRE:
    • Generic Type Parameters are represented by System.Type
    • A Type/MethodInfo object representing a Generic Type/Method has an array of types containing the Generic Type Parameters and Generic Type Arguments

Examples to consolidate our understanding

Consider the followign code snippet. The invariant conditions are shown in the comments below it.

public class Base<T, U>
{
    public static T M1Base(U u) { return default(T); }
}

public class Derived<V> : Base<string, V>
{
    public G<Derived<V>> F;
   
public class Nested
    {
        void M1Nested() { }
    }
   
public static void M1Derived<W>() { }
}

public class G<T> { }

/*
Type: Derived<V>
IsGenericType: True
IsGenericTypeDefinition: True
ContainsGenericParameters: True
IsGenericParameter: False

Type: Base<string, V>
IsGenericType: True
IsGenericTypeDefinition: False
ContainsGenericParameters: True
IsGenericParameter: False

Type: Array of Derived<int>
IsGenericType: False
IsGenericTypeDefinition: False
ContainsGenericParameters: False
IsGenericParameter: False

Type: Type parameter T in Base<T>
IsGenericType: False
IsGenericTypeDefinition: False
ContainsGenericParameters: True
IsGenericParameter: True

Type: Field Derived<V>.F
IsGenericType: True
IsGenericTypeDefinition: False
ContainsGenericParameters: True
IsGenericParameter: False

Type: Nested
IsGenericType: True
IsGenericTypeDefinition: True
ContainsGenericParameters: True
IsGenericParameter: False

Method: T Base<T, U>.M1Base(U u)
IsGenericMethod: False
IsGenericMethodDefinition: False
ContainsGenericParameters: True

Method: void Derived<V>.M1Derived<W>()
IsGenericMethod: True
IsGenericMethodDefinition: True
ContainsGenericParameters: True

Method: void Derived<V>.M1Derived<int>();
IsGenericMethod: True
IsGenericMethodDefinition: False
ContainsGenericParameters: True

Method: void Derived<string>.M1Derived<int>()
IsGenericMethod: True
IsGenericMethodDefinition: False
ContainsGenericParameters: False
*/

Some of the System.Reflection API for Generics

  • System.Type.*Generic* members:
    • [P] ContainsGenericParameters: True if the type is an Open Constructed Generic Type. False otherwise. 
    • [P] GenericParameterAttributes: Gets a combination of System.Reflection.GenericParameterAttributes flags that describe the covariance and special Constraints of a Generic Type Parameter
    • [P] GenericParameterPosition: Gets the position of the Generic Type Parameter in the Generic Type Parameter list of the Generic Type/Method that declared the parameter. 
    • [P] IsGenericParameter: True if the type represents a Generic Type Parameter. False otherwise. 
    • [P] IsGenericType: True if the type represents a Generic Type. False otherwise. 
    • [P] IsGenericTypeDefinition: True if the type represents a Generic Type Definition. False otherwise. 
    • [M] GetGenericArguments: Get the array of types representing Generic Type Parameter/Arguments of the type.
    • [M] GetGenericParameterConstraints: Returns an array of System.Type objects that represent the Constraints of a Generic Type
    • [M] GetGenericTypeDefinition: Gets the Generic Type Definition corresponding to the Generic Type.
    • [M] MakeGenericType: Construct Closed/Open Constructed Generic Type from the Generic Type Definition
  • System.Reflection.MethodInfo.*Generic* members:
    • [P] ContainsGenericParameters: True if the type is an Open Constructed Generic Method. False otherwise.
    • [P] IsGenericMethod: True if the MethodInfo represents a Generic Method. False otherwise.  
    • [P] IsGenericMethodDefinition: True if the MethodInfo represents a Generic Method Definition. False otherwise.  
    • [M] GetGenericArguments: Get the array of types representing Generic Type Parameter/Arguments of the MethodInfo
    • [M] GetGenericMethodDefinition: Gets the Generic Method Definition corresponding to the Generic Method
    • [M] MakeGenericMethod: Construct Closed/Open Constructed Generic Method from the Generic Method Definition

References

If you want to learn more about Reflection & Generics in .NET Framework here are some references:

Finally as an exercise:

Type.IsGenericType Property help page contains an error in the Example-Invariants table. Armed with the above knowledge find it out and let me know! :)

That's all for now... See you in my next post!