Now that we have discussed the why and the how behind Generics in the last month’s issue (Generics in Whidbey, page 94), let’s understand how Generics works internally.
When any Generic aware compiler (like the Whidbey C# compiler) comes across a generic class implementation, it adopts a different path from what the traditional C++ compiler does when it encounters a similar scenario.
When C++ compiler encounters a template class (a generic class in C++), it evaluates the application code base and sees what type of instantiations have been made for the class. For example, a generic queue class, when implemented in C++, and instantiated for int, double and float, will actually make the C++ compiler spit out code for the class thrice, one for each data type. This is how specialization occurs during compile time by the C++ compiler, and thus, when the final executable is produced, it has extra code, making it bulky.
Thus, in C++, template-based programming is a sophisticated macro programming, where template classes work like macros, expanded for a data-type at the compile time, just like it does for a typical macro definition.
On the contrary, in Whidbey, when a generic class is compiled by a Generics-aware compiler it doesn’t spit out data-type specific code. Instead, new opcodes have been introduced to represent the Generics in IL. For instance, when the assembly containing the GenQueue class (which we discussed in the last issue) is opened in ILDASM (.NET’s IL Disassembler), as shown in the screenshot.
See, how the GenQueue class declaration is done. It’s not been specialized. Likewise, the statement:
T arrData = null;
is represented as:
arrData: private !0
In Whidbey, !0 is the new IL representation for a generic placeholder (which is T in our code above). Infact, if you see the IL of the methods, they too will show the use of !0 instead of the specific data-type for which the developer has coded. Thus, in Whidbey, Generics don’t work as macro expansions for data-type specializations, unlike the C++ compiler. Thus, no bulky executable files.
So, when does the specialization occur? It happens when IL is JITted. The Whidbey JITters are responsible for actually specializing the generic class on the fly, caching the specialized native code and then executing it. Any subsequent calls, or usage, of the generic class that has been JITted is served from the cache itself.
Thus, you have better performance.
Now, some developers might question the very being of Generics since declaring everything as System.Object and then using it for any data-type can achieve the same functionality.
Yes, you can do that, but guess what? You incur performance penalty. How? Take our current example of a generic queue. If you were to implement it using System.Object and then use it to add any System.ValueType values, they would need to be boxed since you are storing them as System.Object. Next, when you are expected to return a value, since they are boxed to System.Object, you will need to un-box them by manually doing an explicit cast and then returning the value. The process of boxing and unboxing will take a toll on your application’s performance.
On the other hand, in Generics, the JITters actually specialize to the data-type in question and thus, no boxing/unboxing takes place. And thus, you get a better performance.
Whidbey Generics are going to affect the way you write applications. The FCL (Framework Class Library) is going to have Generic-version of various collection classes. The concept of constraints, which allow you to constraint the placeholder type to a specific kind, allow granular control. All in all, a better technological implementation of a highly useful concept. Hope this article was able to get you warm with Generics and smoothen your transition to them when Whidbey comes out.
Kumar Gaurav Khanna, Run wintoolzone.com