This article takes you to understand the world of C ා DLR

Time:2020-10-23

This article takes you to understand the world of C ා DLR

A long time ago, I wrote an articleDynamic and anonymous type anonymous object transfer parametersI thought the DLR was implemented by reflection. At that time, I took it for granted that only reflection could parse the member information of an object at runtime and call member methods. Later, it is also because other things have not been turned back to fill in this section of knowledge. It is the so-called mending after a sheep is lost. Let’s have a general understanding of DLR.

The full name of DLR is dynamic language runtime. It’s easy to think of a CLR in C, which is called common language runtime. What’s the relationship between the two? Let’s talk about it later

DLR is a new concept introduced by C ා 4.0Dynamic binding and interaction

C ා keyword dynamic

DLR first defines a core type concept, dynamic type. That is, the type determined at runtime, the member information and methods of dynamic type are bound only at runtime. In contrast to CLR static types, static types are matched to the final binding through a series of rules during C ා compilation.

This dynamic binding process is a bit like reflection, but it is very different from reflection. I’ll talk about this a little bit.

Objects made up of dynamic types are called dynamic objects.

DLR generally has the following characteristics:

  1. All types of CLR are implicitly converted todynamic。 asdynamic x = GetReturnAnyCLRType()
  2. Similarly, dynamic can almost be converted to CLR type.
  3. All expressions with dynamic types are dynamically evaluated at run time.

With the development of DLR, we almost all use dynamic type keywordsdynamicAs well as the class library dapper that references DLR.

When we don’t want to create a new static class for dto mapping, we think of dynamic types for the first time. Dynamic is often used as a parameter.

At this time, we should pay attention to some details of dynamic that are not known to most people.

Not all expressions containing dynamic are dynamic.

What do you mean? Look at this codedynamic x = "marson shine";。 This code is very simple, is to assign a string to the dynamic type X.

You should not think that this is a dynamic type. In fact, it is not. If it is just this sentence, the C ා compiler will convert the variable x into the static type object during compilation, which is equivalent toobject x = "marson shine";。 Some people may be surprised why the C ා compiler eventually generates code of type object. That’s what we need to pay attention to next.

The hidden relationship between dynamic and object

In fact, if you take the dynamic type as the parameter, it is actually equal to the object type. In other words, dynamic is an object at the CLR level. In fact, we don’t need to remember this point, we can know from the C ා code generated by the compiler.

Here I use dotpeek to see the C ා code generated by the compiler.

By the way, I would like to ask you if there is any tool for decompiling C ා under Mac. For recommendation

Therefore, when we write overloaded methods, we cannot distinguish them by object and dynamic.

void DynamicMethod(object o);
Void DynamicMethod (dynamic d); // error compiler failed to compile: a method with the same name and formal parameter already exists

If dynamic is the same as object, what does it have to do with DLR?

In fact, Microsoft provides such a keyword, I think it is convenient to provide a shortcut to create dynamic types. What is really closely related to dynamic types is the namespaceSystem.DynamicType under. Main core classesDynamicObject,ExpandoObject,IDynamicMetaObjectProviderWe won’t talk about these three classes in this section.

DLR exploration

First of all, let’s have a general understanding of the important function DLR added by C ා 4.0, and what hierarchy it is in the compiler.

I quote herehttps://www.codeproject.com/Articles/42997/NET-4-0-FAQ-Part-1-The-DLRThe meaning of a structural diagram of this article

Dynamic programming = CLR + DLR

This is enough to show the position of DLR in C ා. Although there is only one letter difference between the name and CLR, the level of DLR is actually above CLR. We know that the compiler converts the code we write into IL, and then converts it into local code through CLR, and the CPU executes the executable program. So actually,DLR does a lot of work during compilation and runtime. Finally, C ා code will be converted to CLR static language, and then converted to local code execution through CLR(such as calling functions).

Now let’s briefly introduce what DLR does during compilation.

Here, we have to give an example to illustrate the above example

// program.cs
dynamic x = "marson shine";
string v = x.Substring(6);
Console.WriteLine(v);

To save space, I simplified and rewritten ugly variable naming and unnecessary annotations. The generated code is as follows:

object obj1 = (object) "marson shine";
    staticCallSite1 = staticCallSite1 ?? CallSite>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "Substring", (IEnumerable) null, typeof (Example), (IEnumerable) new CSharpArgumentInfo[2]
    {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, (string) null)
    }));

    object obj2 = ((Func) staticCallSite1.Target)((CallSite) staticCallSite1, obj1, 6);
    staticCallSite2 = staticCallSite2 ?? CallSite>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", (IEnumerable) null, typeof (Example), (IEnumerable) new CSharpArgumentInfo[2]
    {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
    }));

    ((Action) staticCallSite2.Target)((CallSite) staticCallSite2, typeof (Console), obj2);

The two variables abovestaticCallSite1,staticCallSite2Is a static variable, which acts as a cache.

Three core concepts of DLR are involved here

  1. Expresstree: code is generated and executed using the abstract syntax tree (AST) through the CLR runtime. And it is also the main tool used to interact with dynamic languages (such as python, JavaScript, etc.)
  2. Call site: when we write to call a dynamically typed method, this is a call point. These calls are static functions and can be cached. Therefore, subsequent calls will run faster if they are found to be of the same type.
  3. Binder: in addition to the call point, the system also needs to know how to call these methods, such as through the call in the exampleBinder.InvokeMemberMethods, and the methods that are called by the object type. Binders can also be cached

summary

The DLR running process can be summarized as follows: the DLR is generated during the compilation run timeExpression treeCall pointBinderCode, as well as caching mechanism, we can achieve the reuse of computing to achieve high performance. From Lao Zhao a long time agoExpression tree cache seriesThe article also points out that the performance of caching with expression tree is closest to direct call (excluding IL programming of course).

Now we know why DLR can achieve the same effect as reflection, but the performance is much better than reflection.

Supplementary notes

The students who have just seen the comments mentioned the performance test comparison between reflection and dynamic, and found that reflection performance occupies an obvious advantage. In fact, from that example, it just illustrates the problem of DLR. I’ll list his test code first

const int Num = 1000 * 100;
{
    var mi = typeof(XXX).GetMethod("Go");
    var go1 = new XXX();
    for (int i  = 0; i  < Num; i++)
    {
        mi.Invoke(go1, null);
    }
}
{
    dynamic go1 = new XXX();
    for (int i  = 0; i  < Num; i++)
    {
        go1.Go();
    }
}

In this test, the reflected metadata information has been cached to the local variable MI, so when calling the method, the cached MI is actually used. In the case of no cache advantage, the performance of DLR is inferiorMethodInfo+InvokeYes.

In fact, it is also emphasized in the summary of the article,Using cache mechanism to achieve reuse of repeated calculationsTo improve performance

So we are looking at an example:

public void DynamicMethod(Foo f) {
    dynamic d = f;
    d.DoSomething();
}

public void DynamicMethod(Foo f) {
    var m = typeof(Foo).GetMethod("DoSomething");
    m?.Invoke(f, null);
}

The method dosomething is just an empty method. Now let’s look at the implementation results

//Execution time
var f = new Foo();
Stopwatch sw = new Stopwatch();
int n = 10000000;
sw.Start();
for (int i = 0; i < n; i++) {
    ReflectionMethod(f);
}
sw.Stop();
Console.WriteLine("ReflectionMethod: " + sw.ElapsedMilliseconds + " ms");

sw.Restart();
for (int i = 0; i < n; i++) {
    DynamicMethod(f);
}
sw.Stop();
Console.WriteLine("DynamicMethod: " + sw.ElapsedMilliseconds + " ms");

//Output
ReflectionMethod: 1923 ms
DynamicMethod: 223 ms

Here we can clearly see the implementation time gap. In fact, the execution of DLR is represented by the following pseudo code

public void DynamicMethod(Foo f) {
    dynamic d = f;
    d.DoSomething();
}
//Here's what the DLR will generate
static DynamicCallSite fooCallSite;
public void ReflectionMethod(Foo f) {
    object d = f;
    if(fooCallSite == null) fooCallSite = new DynamicCallSite();
    fooCallSite.Invoke("Foo",d);
}

The compiler is compiling the above methodsDynamicMethodIf so, it will directly call the prepared call point foocallsite. Otherwise, as mentioned earlier in the article, it will generate the call point, and the binder will bind the member information. According to the ast, the expression tree will be generated and all these will be cached. In the calculation (call).

Because we know something about DLR, we also know how to use DLR and the keyword dynamic. For example, we now know that the C ා compiler will treat dynamic as an object. Then we must pay attention to not to be “inexplicably” boxed, resulting in unnecessary performance loss.

As for the application of DLR, especially combined with dynamic language programming, to achieve the purpose of static language dynamic programming. In fact, when DLR just came out, there were open source components like ironpython. This is another topic, and we are doing very few practical applications, so we have not started to talk about it.

Parameter data:

  1. https://www.codeproject.com/Articles/42997/NET-4-0-FAQ-Part-1-The-DLR
  2. In depth understanding of C ා