Fork me on GitHub
12 January 2013

The most important new feature of C# 2.0 (which was released in 2005) is generics. Understanding how they are implemented is essential to understand advances and key features they will bring later to C# and the .NET framework (like LINQ for example).


What are generics?
Also called parametric polymorphism, generics consists in declaring a type with a type parameter that will be instantiated when it will be needed. It adds a great flexibility to the language.
In C#, you can declare generics with the help of < T > where T is the parameterized type. Classes, interfaces, methods, properties and delegates can be defined. The exemple below declares a generic simplified version of the List< T > class (available in the .NET framework)

Then, this List< T > can be used by adding objects to the list and using methods of the list.

An important thing to keep in mind is that parameterized objects has to have the same type. If another class is created, an object of this second class cannot be added to the CarList.
Generic with multiple parameterized types can also be declared, in this example a List< T, U > could be declared.


Constraints can be added to generics through the “where” keyword. It consists in declaring restriction about the parameterized type. For instance, the parameterized type has to be a reference type or it has to derive from a specific class. These constraints avoid compile-time errors. For more information on constraints on type parameters check the C# programming guide on the msdn website.

The website list 6 differents type of constraints:

Constraint

Description

where T: struct

The type argument must be a value type. Any value type except Nullable can be specified.

where T : class

The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.

where T : new()

The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.

where T : <base class name>

The type argument must be or derive from the specified base class.

where T : <interface name>

The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.

where T : U

The type argument supplied for T must be or derive from the argument supplied for U.

The example below shows a generic class declaration where the parameterized type has to implement a specific interface.

We obtained an error with types that don’t implement “ICustomInterface” like string.


The C# design team made the choice to integrate generics directly into the CLR in order to avoid some problem seen in Java (Java doesn’t integrate generics in the JVM and uses type erasure) but also, to guarantee cross language compatibility through the .NET framework. C# use reification for generics and it results in a complete synchronisation between the compile time and the runtime. In other words, generics instantiation appends at runtime and this is only possible because the IL code produce by the CLR is totally type neutral. Have a look at this excellent interview with Anders Hejlsberg (lead architect of C#, but also author of Turbo Pascal and chief architect of Delphi).

The example below shows a generic list of string and how its type information is preserved in the IL code:

  • It can be noticed here the genericity of the IL code. The “ldarg” instruction for instance (line 5, IL_0000) which takes a method argument and put it on the stack, has no specific type to work with.
  • The line 4 shows the list of string but with a number List`1< string >, this number is the arity and correponds to the number of parameterized type used (List< T, U > has an arity of 2).
  • When the parameterized type is used, the IL code add a ! as it can be seen line 11 (IL_0012)

If you have ever played with C# or the .NET framework you should know that System.Object is the base class of every .NET classes. Then, you can think that you could use this class instead of creating generic classes, by creating an object[] for instance. However, there are reasons why you should always use generics instead of System.Object: * No boxing/unboxing operations on value types and no cast operations which increase performance, reduce memory consumption and makes the code more readable * No runtime exeptions, with generics you have a compile-time type check.

The complete technical guide of Generics is available online on the msdn website here.



blog comments powered by Disqus