6

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?

James Wiseman
  • 29,946
  • 17
  • 95
  • 158

2 Answers2

7

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Actually, your `ToString()` trick won't work (unless you also add an explicit cast to `string`). – svick Sep 20 '12 at 20:13
1

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.

Icemanind
  • 47,519
  • 50
  • 171
  • 296
  • You wouldn't see any attribute in this case, because local variables can't have attributes. – svick Sep 20 '12 at 20:14
  • @svick -- No, but properties can have attributes and in his example `Name` is a property – Icemanind Sep 20 '12 at 20:15
  • But the type of that property is `string`, not `object` with `DynamicAttribute`. – svick Sep 20 '12 at 20:17
  • Oh you are right....the `Name` property is string. But the `Container` object would be decorated with a `[dynamic]` attribute tag. – Icemanind Sep 20 '12 at 20:19
  • No, it wouldn't. Objects don't have attributes and neither do locals. The attribute is only necessary if you access the `dynamic` object from the outside, which can't happen if it's a local variable. – svick Sep 20 '12 at 20:22