I recommend reading Generics in C#, Java, and C++: A Conversation with Anders Hejlsberg.
Qn 1. How do generics get compiled by the
JIT compiler?
From the interview:
Anders Hejlsberg: [...]
In the CLR [Common Language Runtime],
when you compile List, or any other
generic type, it compiles down to IL
[Intermediate Language] and metadata
just like any normal type. The IL and
metadata contains additional
information that knows there's a type
parameter, of course, but in
principle, a generic type compiles
just the way that any other type would
compile. At runtime, when your
application makes its first reference
to List, the system looks to see
if anyone already asked for
List<int>
. If not, it feeds into the
JIT the IL and metadata for List<T>
and the type argument int. The JITer,
in the process of JITing the IL, also
substitutes the type parameter.
[...]
Now, what we then do is for all type
instantiations that are value
types—such as List<int>, List<long>,
List<double>, List<float>
—we create a
unique copy of the executable native
code. So List<int>
gets its own code.
List<long>
gets its own code.
List<float>
gets its own code. For all
reference types we share the code,
because they are representationally
identical. It's just pointers.
Qn 2. The thing is that new generic types
can be created in runtime by using
reflection. Which can of course affect
the generic's constraints. Which
already passed the semantic parser.
Can someone explain how this is
handled?
Essentially, IL retains a high-level view of generic types, which allows the CLR to check constraints for 'dynamically constructed' generic types at run-time just like the C# compiler might do for 'statically constructed' types in C# source-code at compile-time.
Here's another snippet (emphasis mine):
Anders Hejlsberg: [...]
With a constraint, you can hoist that
dynamic check out of your code and
have it be verifiable at compile time
or load time. When you say K must
implement IComparable, a couple of
things happen. On any value of type K,
you can now directly access the
interface methods without a cast,
because semantically in the program
it's guaranteed that it will implement
that interface. Whenever you try and
create an instantiation of that type,
the compiler will check that any type
you give as the K argument implements
IComparable, or else you get a compile
time error. Or if you're doing it with
reflection you get an exception.
Bruce Eckel: You said the compiler and
the runtime.
Anders Hejlsberg: The compiler checks
it, but you could also be doing it at
runtime with reflection, and then the
system checks it. As I said before,
anything you can do at compile time,
you can also do at runtime with
reflection.