30

I know that generics are compiled by JIT (like everything else), in contrast to templates that are generated when you compile the code.
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 ? And what exactly happens ?
(Both the code generation and semantic check)

Kevin Montrose
  • 22,191
  • 9
  • 88
  • 137
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
  • 3
    Constraints are not just enforced by the compiler, the jitter checks them too. There are several not-so-trivial aspects to generics, how they get ngen-ed is particularly boggling. It *did* take them 5 years. – Hans Passant Mar 17 '11 at 18:55

2 Answers2

38

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.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Ani
  • 111,048
  • 26
  • 262
  • 307
2

Reference types generics all become the same type; value type generics are instantiated separately.

This is because reference types are all really just Object references (4 or 8 bytes), whereas value types are different and cannot be handled by a single piece of code, due to stack layout differences, etc. Therefore, instantiating multiple copies of a generic type with value types will increase the memory usage by a lot, whereas instantiating multiple copies with reference types won't.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 2
    They aren't the same type. They share the same machine code for member functions (methods)... but each is a distinct type with its own copy of static member variables. – Ben Voigt Aug 20 '13 at 19:50