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)
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 forList<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. SoList<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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With