Generics Implementation


On the surface C# generics look syntactically similar to C++ templates, but there are important differences in the way they are implemented and supported by the compiler. This has significant implications for how you use generics.

Compared to C++ templates, C# generics can provide enhanced safety but are also somewhat limited in capabilities. In some C++ compilers, until you use a template class with a specific type, the compiler does not even compile the template code. When you do specify a type, the compiler inserts the code inline, replacing every occurrence of the generic type parameter with the specified type. In addition, every time you use a specific type, the compiler inserts the type-specific code, regardless of whether you have already specified that type for the template class somewhere else in the application. It is up to the C++ linker to resolve this, and it is not always possible to do so. This may result in code bloating, increasing both the load time and the memory footprint.

In .NET 2.0, generics have native support in the IL and the CLR itself. When you compile generic C# server-side code, the compiler compiles it into IL, just like it would any other type. However, the IL only contains parameters or placeholders for the actual specific types. In addition, the metadata of the generic server contains generic information.

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 a 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 .NET compiles 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 types. If the client specifies a value type, the JIT compiler replaces the generic type parameters in the IL with the specified value type and compiles the IL into native code. However, the JIT compiler keeps track of type-specific server code it has already generated. Thus, 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, 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 requests 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 off the managed heap according to their size, and there is no casting.

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 and then 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 downcasting of reference types, performance is greatly improved. With value types there is typically a 200% performance gain, and with reference types you can expect up to a 100% performance gain in accessing the type (of course, the application as a whole may or may not experience any performance improvements).



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net