Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# dynamic type causes Console.WriteLine to be resolved with reflection in IL

I've been playing around with some C# statements in LINQPad with a view to understanding what intermediate language code is emitted.

I first tried the following code:

var Container = new {Name = "James"};
Console.WriteLine(Container.Name);

And saw the following six lines of IL emitted:

IL_0001:  ldstr       "James"
IL_0006:  newobj      <>f__AnonymousType0<System.String>..ctor
IL_000B:  stloc.0     
IL_000C:  ldloc.0     
IL_000D:  callvirt    <>f__AnonymousType0<System.String>.get_Name
IL_0012:  call        System.Console.WriteLine

Which, is broadly what I expect, and is quite a nice demonstration of how anonymous types are read-only/immutable, seeing as there is no set_Name property.

Next I tried the statements:

dynamic Container = new System.Dynamic.ExpandoObject();
Container.Name = "James";
Console.WriteLine(Container.Name);

Which causes a huge amount of IL to be emitted. I'll not paste it here, but you can find it in this pastebin.

I understand there is quite a bit of overhead with regard to managing the dynamic type and ExpandoObject, but I don't understand why it appears that the call to System.Console.WriteLine is in this case performed through internal reflection.

IL_0072:  ldstr       "WriteLine"
....
IL_00BF:  ldtoken     System.Console

In the first segment of code, after the property was retrieved and stored, it was a one-line IL statement that invoked System.Console.WriteLine.

So why is all this extra required for the call with a dynamic type?

like image 395
James Wiseman Avatar asked Sep 20 '12 19:09

James Wiseman


2 Answers

Because the variable is dynamic there is no way to know, at compile time, which overload of WriteLine should be called. It's not until runtime that we know the actual type of the dynamic object. Because of the way dynamic works, it's important that it not just be treated as an object at compile time; part of the power is that it is determining the correct overload at runtime.

If you cast the object to something other than dynamic (i.e. string after calling ToString or just back to ExpandoObject) and then pass it to WriteLine then you should see that reflection call go away and see it statically determine, at compile time, the proper overload of WriteLine.

like image 163
Servy Avatar answered Sep 30 '22 14:09

Servy


What's happening is the compiler is creating your code in such a way that it can be "late-binded". Late binding means that rather then resolve your objects during compilation, as with traditional data types and objects, the object is being resolved at run time, while your assembly is actually in memory and running.

If you were to look at your code in Reflector or dotPeek, you would see that your dynamic objects were decorated with a [Dynamic] attribute. While your program is running in memory, when it comes to an object that has been decorated with this attribute, the call to this object is piped through a dynamic Container (or whatever your object is called). This Container is initialized with the Binder responsible for run time binding. That is what all the called to Microsoft.CSharp.RuntimeBinder does. This RuntimeBinder is used later to invoke properties or methods or whatever is dynamic.

I hope this clears things up a bit. I'm typing on my android, so the explanation might be less than ideal. I will clean it up later.

like image 41
Icemanind Avatar answered Sep 30 '22 14:09

Icemanind