the client-side compiler uses that generic metadata to support type safety. when the client provides a
specific type instead of a generic type parameter, the client's compiler substitutes the generic type
parameter in the server metadata with the specified type. this provides the client's compiler with
type-specific definition of the server, as if generics were never involved. this way the client compiler can
enforce correct method parameters, type-safety checks, and even type-specific intellisense®.
the interesting question is how does .net compile the generic il of the server to machine code. it turns
out that the actual machine code produced depends on whether the specified types are value or reference
type. if the client specifies a value type, then the jit compiler replaces the generic type parameters in the
il with the specific value type, and compiles it to native code. however, the jit compiler keeps track of
type-specific server code it already generated. if the jit compiler is asked to compile the generic server
with a value type it has already compiled to machine code, it simply returns a reference to that server
code. because the jit compiler uses the same value-type-specific server code in all further encounters,
there is no code bloating.
if the client specifies a reference type, then the jit compiler replaces the generic parameters in the
server il with object, and compiles it into native code. that code will be used in any further request for
a reference type instead of a generic type parameter. note that this way the jit compiler only reuses
actual code. instances are still allocated according to their size off the managed heap, and there is no
casting.
generics benefits
generics in .net let you reuse code and the effort you put into implementing it. the types and internal
data can change without causing code bloat, regardless of whether you are using value or reference
types. you can develop, test, and deploy your code once, reuse it with any type, including future types,
all with full compiler support and type safety. because the generic code does not force the boxing and
unboxing of value types, or the down casting of reference types, performance is greatly improved. with
value types there is typically a 200 percent performance gain, and with reference types you can expect
up to a 100 percent performance gain in accessing the type (of course, the application as a whole may or
may not experience any performance improvements). the source code available with this article includes
a micro-benchmark application, which executes a stack in a tight loop. the application lets you
experiment with value and reference types on an object-based stack and a generic stack, as well as
changing the number of loop iterations to see the effect generics have on performance.
applying generics
because of the native support for generics in the il and the clr, most clr-compliant language can take
advantage of generic types. for example, here is some visual basic® .net code that uses the generic
stack of code block 2:
dim stack as stack(of integer)
stack = new stack(of integer)
stack.push(3)
dim number as integer
number = stack.pop()
you can use generics in classes and in structs. here is a useful generic point struct:
public struct point<t>
{
public t x;
public t y;
}
you can use the generic point for integer coordinates, for example:
point<int> point;
point.x = 1;
point.y = 2;
or for charting coordinates that require floating point precision:
point<double> point;
point.x = 1.2;
point.y = 3.4;
besides the basic generics syntax presented so far, c# 2.0 overloads some of the syntax of c# 1.1 to
specialize it when using generics. for example, consider the pop() method of code block 2. suppose
instead of throwing an exception when the stack is empty, you would like to return the default value of
the type stored in the stack. if you were using an object-based stack, you would simply return null, but
a generic stack could be used with value types as well. to address this issue, every generic type
parameter supports a property called default, which returns the default value of a type.
here is how you can use default in the implementation of the pop() method:
public t pop()
{
m_stackpointer--;
if(m_stackpointer >= 0)
{
return m_items[m_stackpointer];
}
else
{
m_stackpointer = 0;
return t.default;
}
}
the default value for reference types is null, and the default value for value types (such as integers,
enum, and structures) is a zero whitewash (filling the structure with zeros). consequently, if the stack is
constructed with strings, the pop() methods return null when the stack is empty, and when the stack is