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?
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
.
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.
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