When the CLR builds our application, it will make an internal structure for each type we use in the application. The generic types we create are no different, on the condition that the generic type parameters have values assigned to them. If these parameters don’t have values (data types) assigned to them they are called an “open type” and the CLR will not allow the construction of such a type (because basically, the CLR doesn’t know what to build exactly). If the data type parameters are properly filled in, the CLR will create internal types for these objects. This is called a “closed type”.
When the compiling code that uses generic type parameters, the compiler generates IL, fills in the parameters and generates native code specific to that method for those types. This is exactly the same as how other code is compiled and is an advantage of generics (it behaves as you would expect). Of course, since no good deed goes unpunished, there is a down side to this. The CLR keeps generating native code for every method-type combination possible. This can increase the amount of memory required by your application enormously. This is called “code explosion”.
At Microsoft they were fully aware of this, so the CLR has built in optimizations to counter code explosion. The CLR will keep track of the method-type combinations it generates code for. If the same method-type combination is used somewhere else in the code, it will use the already generated IL-code. If you have several assemblies in your application that use List<string>, the CLR will emit code just once and re-use this code. Also, if you are using reference type arguments, the code for these types are only generated once. This is because the CLR thinks of all reference type arguments as the same (to the CLR these are all just pointers). Unfortunately, the issue with value types remains. Here the CLR must emit code for each type individually. Even if these types are basically the same (e.g.: Int32 and UInt32), every type will have its own native code since different native processor-instructions can be used to work with these values.
The following is something that you may not have heard of by name, but you’ve certainly used it, which is “type inference”. This concept is supported by the C# compiler and allows you not to define every type when assigning it.
For example:
Int32 number = 5;
String someText = “Some text”;
Here the compiler will check the type of the value (let’s say: “Some Text”), it will recognize the value as type String and see that you want to assign it to an object of type String, and so the compiler will assign the value to the object without complaining. You can see the problem that might arise when using generics. If you call a generic method without specifying a type, you will get a compiler error (error CS0411). I got a great example for this in Jeffrey Richter's book CLR via C#:
1: private static void Display(String s)
2: {
3: Console.WriteLine(s);
4: }
5:
6: private static void Display<T>(T o)
7: {
8: Display(o.ToString());
9: }
10:
11: //In main:
12: Display("Jeff");
13: Display(123);
14: Display<String>("Aidan");
The compiler always looks for an exact match when calling methods. So the first call will go directly to the Display()-method that takes a string as an argument. And the third call will go to the generic overload of the Display()-method. The nice thing here is the second call. There is no specific overload of the Display()-method that takes an integer as a parameter. So, the compiler will settle for the generic match. It can define the type of “123” (which is Int32), so it can call the generic overloaded version of the Display()-method and gives the following output (I’ve added some extra text to point out what method is being called):
