📜 ⬆️ ⬇️

The dangers of not looking ahead

At first glance, dynamic in C # is just an object with support for the compiler's machinery. But not really.

The core of the runtime is the DLR (Dynamic Language Runtime) - a subsystem / framework for supporting dynamic programming languages. There is an implementation for C # itself, which comes in delivery with .NET, and is separate for Iron-languages.

When we work with generics, the CLR has its own optimizations for this specialization. The moment CLR + DLR should work with generics together, the behavior of written code can become unpredictable .

Preamble


First you need to remember how the generalizations of CLR are supported.
Each generic type has its own implementation, i.e. missing type-erasure. But for reference types, the environment uses the System .__ Canon type to share code. This is necessary not so much because of obviousness (each object is a reference the size of a machine word), but rather to allow cyclic dependencies between types.
')
I already wrote about this:
The fact is that generic types can contain cyclical dependencies on other types, which is fraught with the endless creation of specializations for code. For example:
Generics cyclomatic dependencies
class GenericClassOne<T> { private T field; } class GenericClassTwo<U> { private GenericClassThree<GenericClassOne<U>> field } class GenericClassThree<S> { private GenericClassTwo<GenericClassOne<S>> field } class Program { static void Main(string[] args) { Console.WriteLine((new GenericClassTwo<object>()).ToString()); Console.Read(); } } 


However, this code will not fall and will output GenericClassTwo`1 [System.Object] .

A type loader (also known as a type loader) scans each generic type for the presence of a circular dependency and assigns a priority (the so-called LoadLevel for a class). Although all specializations for ref-types have System .__ Canon as a type argument - this is a consequence, not a reason.

Loading phases (they are also ClassLoadLevel):

 enum ClassLoadLevel { CLASS_LOAD_BEGIN, CLASS_LOAD_UNRESTOREDTYPEKEY, CLASS_LOAD_UNRESTORED, CLASS_LOAD_APPROXPARENTS, CLASS_LOAD_EXACTPARENTS, CLASS_DEPENDENCIES_LOADED, CLASS_LOADED, CLASS_LOAD_LEVEL_FINAL = CLASS_LOADED, }; 


Infinite loop


Since such a feature exists for generalizations, respectively, and other subsystems must also follow this rule. But DLR is an exception.

Consider the class hierarchy:
NB : the real code is from the structuremap project, although it has undergone changes at this point. The example was used during my talk “Effective use of the DLR”.

 public class LambdaInstance<T> : LambdaInstance<T, T> { } public class LambdaInstance<T, TPluginType> : ExpressedInstance<LambdaInstance<T, TPluginType>, T, TPluginType> { } public abstract class ExpressedInstance<T> { } public abstract class ExpressedInstance<T, TReturned, TPluginType> : ExpressedInstance<T> { } 

And the code itself:

 class Program { static LambdaInstance<object> ShouldThrowException(object argument) { throw new NotImplementedException(); } static void Main(string[] args) { //    ? ShouldThrowException((dynamic)new object()); } } 

Question: Will an exception be thrown ?
The answer is no . The method Should ThrowException never ends. And stackoverflow ( transfer to the site ) will not happen.

Hmm ... So what's the deal? - you ask.
It's simple - LambdaInstance <object> . Consider the class hierarchy again.

LambdaInstance <T> is inherited from LambdaInstance <T, TPluginType> , which in turn is from ExpressedInstance < LambdaInstance <T, TPluginType> , T, TPluginType>.

Nested inheritance noticed?

As mentioned above, the CLR has optimized for cyclic type dependencies.

For the expression ShouldThrowException((dynamic)new object()); The DLR should inspect the code point / method signature. In this process, LambdaInstance <object> is encountered and the code turns into an infinite loop.

Why not crush? DLR does not use recursion. Moreover, memory consumption is growing (because additional metadata is being created), but not much.

Epilog


It may seem that dynamic, as such, is a dangerous thing. Next time we will look at an example where its use is correct.

Source: https://habr.com/ru/post/281274/


All Articles