Generics in the runtime (C# programming guide)

When a generic type or method is compiled into common intermediate language (CIL), it contains metadata that identifies it as having type parameters. How the CIL for a generic type is used differs based on whether the supplied type parameter is a value type or reference type.

When a generic type is first constructed with a value type as a parameter, the runtime creates a specialized generic type with the supplied parameter or parameters substituted in the appropriate locations in the CIL. Specialized generic types are created one time for each unique value type that is used as a parameter.

For example, suppose your program code declared a stack that's constructed of integers:

Stack<int>? stack;

At this point, the runtime generates a specialized version of the Stack<T> class that has the integer substituted appropriately for its parameter. Now, whenever your program code uses a stack of integers, the runtime reuses the generated specialized Stack<T> class. In the following example, two instances of a stack of integers are created, and they share a single instance of the Stack<int> code:

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

However, suppose that another Stack<T> class with a different value type such as a long or a user-defined structure as its parameter is created at another point in your code. As a result, the runtime generates another version of the generic type and substitutes a long in the appropriate locations in CIL. Conversions are no longer necessary because each specialized generic class natively contains the value type.

Generics work somewhat differently for reference types. The first time a generic type is constructed with any reference type, the runtime creates a specialized generic type with object references substituted for the parameters in the CIL. Then, every time that a constructed type is instantiated with a reference type as its parameter, regardless of what type it is, the runtime reuses the previously created specialized version of the generic type. This is possible because all references are the same size.

For example, suppose you had two reference types, a Customer class and an Order class, and also suppose that you created a stack of Customer types:

class Customer { }
class Order { }
Stack<Customer> customers;

At this point, the runtime generates a specialized version of the Stack<T> class that stores object references that will be filled in later instead of storing data. Suppose the next line of code creates a stack of another reference type, which is named Order:

Stack<Order> orders = new Stack<Order>();

Unlike with value types, another specialized version of the Stack<T> class is not created for the Order type. Instead, an instance of the specialized version of the Stack<T> class is created and the orders variable is set to reference it. Suppose that you then encountered a line of code to create a stack of a Customer type:

customers = new Stack<Customer>();

As with the previous use of the Stack<T> class created by using the Order type, another instance of the specialized Stack<T> class is created. The pointers that are contained therein are set to reference an area of memory the size of a Customer type. Because the number of reference types can vary wildly from program to program, the C# implementation of generics greatly reduces the amount of code by reducing to one the number of specialized classes created by the compiler for generic classes of reference types.

Moreover, when a generic C# class is instantiated by using a value type or reference type parameter, reflection can query it at run time and both its actual type and its type parameter can be ascertained.

See also