Generics in the Runtime (C# Programming Guide)
When a generic type or method is compiled into Microsoft intermediate language (MSIL), it contains metadata that identifies it as having type parameters. How the MSIL for a generic type is used differs based on whether or not the supplied type parameter is a value 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 places in the MSIL. Specialized generic types are created once for each unique value type used as a parameter.
For example, suppose your program code declared a stack constructed of integers, like this:
Stack<int> stack;
At this point, the runtime generates a specialized version of the Stack<T> class with 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, if at another point in your program code another Stack<T> class is created, this time with a different value type such as a long or a user-defined structure as its parameter, the runtime generates another version of the generic type, this time substituting a long in the appropriate places in MSIL. Conversions are no longer necessary because each specialized generic class natively contains the value type.
Generics work a little 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 MSIL. Then, each time 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 further 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, instead of storing data, stores object references that will be filled in later. Suppose the next line of code creates a stack of another reference type, called 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. Rather, an instance of the specialized version of the Stack<T> class is created and the orders
variable is set to reference it. Suppose 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 with the Order
type, another instance of the specialized Stack<T> class is created, and the pointers 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 code bloat 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 with a type parameter, be it a value or reference type, it can be queried at runtime using reflection and both its actual type as well as its type parameter can be ascertained.
See Also
Reference
Introduction to Generics (C# Programming Guide)
System.Collections.Generic