List<double> doubleList = new List<double>();
List<int> markers = new List<int>();
List<MyStruct> values = new List<MyStruct>();
This may be interesting, but why should you care? Generic types that will
be used with multiple different reference types do not affect the memory
footprint. All JIT-compiled code is shared. However, when closed generic
types contain value types as parameters, that JIT-compiled code is not shared.
Let’s dig a little deeper into that process to see how it will be affected.
When the runtime needs to JIT-compile a generic definition (either a
method or a class) and at least one of the type parameters is a value type,
it goes through a two-step process. First, it creates a new IL class that rep-
resents the closed generic type. I’m simplifying, but essentially the run-
time replaces
T with int, or the appropriate value type, in all locations in
the generic definition. After that replacement, it JIT-compiles the necessary
code into x86 instructions. This two-step process is necessary because the
JIT compiler does not create the x86 code for an entire class when loaded;
instead, each method is JIT-compiled only when first called. Therefore, it
makes sense to do a block substitution in the IL and then JIT-compile the
resulting IL on demand, as is done with normal class definitions.
This means that the runtime costs of memory footprint add up in this
way: one extra copy of the IL definition for each closed generic type that
uses a value type, and a second extra copy of machine code for each
method called in each different value type parameter used in a closed
generic type.
There is, however, a plus side to using generics with value type parameters:
You avoid all boxing and unboxing of value types, thereby reducing the
size of both code and data for value types. Furthermore, type safety is
ensured by the compiler; thus, fewer runtime checks are needed, and that
reduces the size of the codebase and improves performance. Furthermore,
as discussed in Item 8, creating generic methods instead of generic classes
can limit the amount of extra IL code created for each separate instantia-
tion. Only those methods actually referenced will be instantiated. Generic
methods defined in a nongeneric class are not JIT-compiled.
This chapter discusses many of the ways you can use generics and explains
how to create generic types and methods that will save you time and help
you create usable components. I also cover when and how to migrate .NET
1.x types (in which you use
System.Object) to .NET 2.0 types, in which
you specify type parameters.
Working with Generics
❘
3