Generics
Thursday, February 21, 2008 3:36:55 PM
Many programming languages handle variables and objects by defining specific types and strict rules about converting between types. Code that is written in a strongly typed language lacks something in terms of generalization, however.
Consider the following code:
int Min( int a, int b ) {
if (a < b) return a;
else return b;
}
To use this code, we need a different version of Min for each type of parameter we want to compare. Developers who are accustomed to using objects as placeholders for a generic type (which is common with collections) might be tempted to write a single Min function such as this:
object Min( object a, object b ) {
if (a < b) return a;
else return b;
}
Unfortunately, the less than operator (<) is not defined for the generic object type. We need to use a common (or “generic”) interface to do that:
IComparable Min( IComparable a, IComparable b ) {
if (a.CompareTo( b ) < 0) return a;
else return b;
}
However, even if we solve this problem, we are faced with a bigger issue: the indeterminate result type of the Min function. A caller of Min that passes two integers should make a type conversion from IComparable to int, but this might raise an exception and surely would involve a CPU cost:
int a = 5, b = 10; int c = (int) Min( a, b );
C# 2.0 solved this problem with generics. The basic principle of generics is that type resolution is moved from the C# compiler to the jitter. Here is the generic version of the Min function:
T Min<T>( T a, T b ) where T : IComparable<T> {
if (a.CompareTo( b ) < 0) return a;
else return b;
}
Note The jitter is the run-time compiler that is part of the .NET runtime. It translates intermediate language (IL) code to machine code. When you compile .NET source code, the compiler generates an executable image containing IL code, which is compiled in machine code instructions by the jitter at some point before the first execution.
Moving type resolution to the jitter is a good compromise: the jitter can generate many versions of the same code, one for each type that is used. This approach is similar to a macro expansion, but it differs in the optimizations used to avoid code proliferation-all versions of a generic function that use reference types as generic types share the same compiled code, while the difference is maintained against callers.
With generics, instead of this:
int a = 5, b = 10; int c = (int) Min( a, b );
you can write code such as this:
int a = 5, b = 10; int c = Min<int>( a, b );
The cast for Min results has disappeared, and the code will run faster. Moreover, the compiler can infer the generic T type of the Min function from the parameters, and we can write this simpler form:
int a = 5, b = 10; int c = Min( a, b );
Type Inference Type inference is a key feature. It allows you to write more abstract code, making the compiler handle details about types. Nevertheless, the C# implementation of type inference does not remove type safety and can intercept wrong code (for example, a call that uses incompatible types) at compile time.
Generics can also be used with type declarations (as classes and interfaces) and not only to define generic methods. As we said earlier, a detailed explanation of generics is not the goal of this book, but we want to emphasize that you have to be comfortable with generics to work well with LINQ.






