got net?

Kevin Hazzard's Brain Spigot

About the author

Welcome to Kevin Hazzard's blog.
E-mail me Send mail

Recent posts

Recent comments

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

C#'s Dynamic Type is NOT Just an Object

Consider the following C# 4 code that creates a null dynamic field and a null object field, then attempts to invoke GetType() through each:

using System;

public class Program
{
    private dynamic i;
    private object j;
    public static void Main()
    {
        var app = new Program();
        try { Console.WriteLine(app.i.GetType()); }
        catch (Exception e)
        {
            Console.WriteLine("{0} says:\r\n\t{1}",
                e.GetType(), e.Message);
        }
        try { Console.WriteLine(app.j.GetType()); }
        catch (Exception e)
        {
            Console.WriteLine("{0} says:\r\n\t{1}",
                e.GetType(), e.Message);
        }
        Console.ReadLine();
    }
}

The console output of this small program is the following text:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException says:
        Cannot perform runtime binding on a null reference
System.NullReferenceException says:
        Object reference not set to an instance of an object.

Why the different treatment between objects and dynamics? After all, we hear all the time that the new dynamic type in C# 4 is really just an object with special runtime behaviors ascribed to it. But the difference in the exception types makes it clear that dynamic types are not plain, old object references. The IL for this code makes it very clear what's going on. I've included the disassembled IL for Main generated by the ILDASM tool below. You'll see that the dynamic field is really a CallSite object. The CallSite class does a lot of interesting things which I'll be blogging about now that the VS2010 Beta 1 is publicly available.

Even if you don't understand what the following code is doing, find the first try block in the code and compare it to the second one. You can see in the second try block that the access to the plain, old object field is very straightforward, just a couple of lines of code. Since the object reference is initialized to null, when the GetType() method is invoked through it, a NullReferenceException is thrown. Standard stuff since .NET 1.0. But in the first try block, where the dynamic field is used to invoke GetType(), the call is dispatched through a compile-time constructed CallSite. So, the CallSite reference is not null which is why we don't get a NullReferenceException. Instead, the CallSite attempts to bind to a real object to dispatch the call and finds that there is nothing there. Hence, the RuntimeBinderException. I think it's important to know that dynamics are not just objects with special runtime behaviors attached to them. I know the following IL code is long and ugly but do this: search in the following text for the word GetType in double quotes. Look at how that string is being passed to the runtime binder in the dynamic case. This is very much like using eval() in JavaScript to inject strings of code into the browser at runtime. C# 4 truly is a dynamic language now.

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       283 (0x11b)
  .maxstack  12
  .locals init ([0] class Program app,
           [1] class [mscorlib]System.Exception e,
           [2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
  IL_0000:  nop
  IL_0001:  newobj     instance void Program::.ctor()
  IL_0006:  stloc.0
  .try
  {
    IL_0007:  nop
    IL_0008:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,object>> Program/'<Main>o__SiteContainer0'::'<>p__Site1'
    IL_000d:  brtrue.s   IL_004e
    IL_000f:  ldc.i4.0

<snip/>

    IL_006c:  brtrue.s   IL_00a2
    IL_006e:  ldc.i4.0
    IL_006f:  ldstr      "GetType"
    IL_0074:  ldtoken    Program
    IL_0079:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    IL_007e:  ldnull
    IL_007f:  ldc.i4.1
    IL_0080:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo

<snip/>

    IL_00df:  leave.s    IL_00e1
  }  // end handler
  IL_00e1:  nop
  .try
  {
    IL_00e2:  nop
    IL_00e3:  ldloc.0
    IL_00e4:  ldfld      object Program::j
    IL_00e9:  callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
    IL_00ee:  call       void [mscorlib]System.Console::WriteLine(object)

<snip/>

  IL_0119:  pop
  IL_011a:  ret
} // end of method Program::Main


Tags:
Categories: C#
Posted by kevin on Thursday, May 21, 2009 7:16 AM
Permalink | Comments (7) | Post RSSRSS comment feed

Comments

Chris Eargle United States

Tuesday, June 02, 2009 11:12 AM

Chris Eargle

I disagree that the dynamic field is really a CallSite object.

The dynamic field is an object with the DynamicAttribute decoration, not an instance of CallSite. This field is passed into the CallSite instance's Target method.

Kevin Hazzard United States

Tuesday, June 02, 2009 6:34 PM

Kevin Hazzard

True, Chris. My wording was not as precise as it should have been. The compiler does create a reference to a System.Object and mark it with the DynamicAttribute as you say. However, the treatment of that object at compile time (not just runtime) is radically different. What I so imprecisely tried to point out was that the compiled IL for this code includes the creation of a CallSite and all the code necessary to invoke the RuntimeBinder on the target object. You are right that the dynamic object reference is not to an actual CallSite. When I said "CallSite reference" I meant the Target object. Poor wording on my part. Thank you for the clarification KodeFuGuru. Smile

Chris Eargle United States

Wednesday, June 03, 2009 3:24 PM

Chris Eargle

True, the treatment of the dynamic object at compile time is radically different than the compiler's treatment of object. The wording made me do a head fake, and I had to make sure the behavior wasn't changed since the last time I looked at it Smile.

I never noticed the DynamicAttribute before, but I see why it's necessary. If you're reflecting the class, it would be important to know that the field is dynamic. If a property (or, ::shudder::, nonprivate field) is marked with the dynamic attribute, then the compiler will need to know how to handle it.

Kevin Hazzard United States

Wednesday, June 03, 2009 6:31 PM

Kevin Hazzard

The DynamicAttribute is interesting. I am not really sure why it's needed because you can see that the CallSites are wired up at compile time. The dynamic object reference is also pre-wired up to be inserted as the Target, too. So, why is it marked with the DynamicAttribute? Not sure about that, to be honest.

Chris Eargle

Thursday, June 04, 2009 11:10 AM

Chris Eargle

Yes, the callsites are wired up at compile time, but what happens when you add the assembly as a reference to another project? Without DynamicAttribute, the compiler would treat it as an object rather than dynamic. (that's how it shows up in intellisense).

I thought I could use this to treat an object as static in the local assembly and dynamic in a calling assembly. Unfortunately, I get the following error when attempting to use DynamicAttribute in my C# code: Do not use 'System.Runtime.CompilerServices.DynamicAttribute'. Use the 'dynamic' keyword instead.

Kevin Hazzard United States

Thursday, June 04, 2009 4:21 PM

Kevin Hazzard

That's very interesting. And true, the compiler does need to know that across assemblies. Is that a runtime error or a compile-time error? If it's compile-time, how do they enforce that? I'd like to be able to mimic that behavior for some of my custom, private attributes.

Chris Eargle United States

Thursday, June 04, 2009 6:17 PM

Chris Eargle

It's a compile-time error. I suspect they're doing it in the compiler as there's nothing suspect being reflected from that class.

I posted a blog about DynamicAttribute: www.kodefuguru.com/.../DynamicAttribute.aspx

Comments are closed