C# 4.0 New Features Part 1 – dynamic keyword

Writing

UPDATE: I have posted some more performance numbers on the dynamic keyword in a second post

One of the coolest new features in C# 4.0 that has been announced at PDC is the new dynamic keyword. This keyword allows the developer to declare an object whose method calls will be resolved at runtime. The interesting part about it is that the class doesn’t need to be declared in any special way to use this keyword, it is all up to the consumer.

So, lets just declare a normal class like this:

public class TestClass
{
    public void TestMethod1()
    {
        Console.WriteLine("test method 1");
    }

    public void TestMethod2()
    {
        Console.WriteLine("test method 2");
    }        
}

And now we could instantiate and call a few methods on this class just like this:

var test = new TestClass();
test.TestMethod1();
test.TestMethod2();

At this point everything is working exactly as you expect it, and it all compiles just fine. But now, lets throw in the dynamic keyword:

dynamic test = new TestClass();
test.TestMethod1();
test.TestMethod2();

Okay, so nothing changed, right? Wrong. Everything builds just as before, but now those method calls on our test method are not being resolved at compile time, they are being resolved at runtime! So, say we did this:

dynamic test = new TestClass();
test.TestMethod1();
test.TestMethod2();
test.TestMethod3();

This would still compile! But at runtime we would see something like this:

Runtime Error

Pretty big implications, huh? So, what is happening here? This is where our friend Reflector comes in:

private static void Main(string[] args)
{
    object test = new TestClass();
    if (<Main>o__SiteContainer0.<>p__Site1 == null)
    {
        <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), false, false, "TestMethod1", typeof(object), null));
    }
    <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, test);
    if (<Main>o__SiteContainer0.<>p__Site2 == null)
    {
        <Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), false, false, "TestMethod2", typeof(object), null));
    }
    <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, test);
}

So this is what our main method looks like when we reflect it. It may be hard to follow, but make sure you look at the line numbers on the side to see where the wrapping occurs. First the compiler generated the “__SiteContainer0” local field in order to hold our callsite info. Next you will see that our “test” variable is just of class “object” now! There really isn’t a dynamic type, it is just a helper.

Next you see that we are checking if these callsites are null, and if they are, then we are using “CallSite.Create” and passing in a “CSharpCallPayload” object which has all the info about the method that we are trying to call. Once the callsite is defined, then we just invoke that callsite on our “test” instance by passing the callsite data and the object. The compiler has done all of this for us, we just need to sit back and let it happen.

So, in this instance what we are doing is pretty useless, but the power of this feature comes in when we are using a type whose methods we do not necessarily know at compile time. This could be because it is coming from some dynamic code (like IronRuby!), or it is a generated class that we don’t have compile time type info for, or anywhere that you are currently using heavy reflection for.

So, the first thing that popped into my mind when I saw this was, “what are the performance implications of this?” I know that this is a super early CTP, but I figured that I would run a few unscientific tests anyways.

So, here is the highly scientific process I used:

I changed to Release build and I put the two method calls in a loop surrounded by a stopwatch. The loop ran 1 million times, and so invoked a total of 2 million method calls. I then wrote out the number of milliseconds it took to execute to the screen. I also modified the TestClass to not write to the screen, since that would take significantly more time than the method calls. Instead, I changed the class to just add numbers, like this:

public class TestClass
{
    public int i;
    public void TestMethod1()
    {
        i += 1;
    }

    public void TestMethod2()
    {
        i += 2;
    }        
}

I ran each of them once, and then threw away the first result. I then ran them each 7 times and threw away the highest and the lowest. Then averaged the 5 numbers left.

Compile Time Bound: 6 ms – yep, 2 million calls, 6 ms. Fast.

Dynamically Bound: 2106ms – so, roughly 351 times slower than the strongly typed calls

Now, this is a super early CTP and they will obviously optimize this, but they still are probably going to be many times slower than the compile time calls. The thing to realize here is that while these numbers are very different, we are still talking about 2 million calls in about 2 seconds. So, about one thousandth of a millisecond per call.

In most applications we are using reflection because we have to, or because it solves a problem that would be much harder to solve with strongly typed code. The overhead of making these kinds of calls is small, and unless they are being called with extremely high frequency would likely not make a noticeable impact on your application.

I hope that you enjoyed this quick little spin around the dynamic keyword, and I’ll be sure to come back shortly with more C# 4.0 goodies!

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Comments (12)

  1. When I started to read this post one question where in my mind is he going to talk about performance? Then just before you finish your post you hit my question. Thank you Justin. Really nice post

  2. Thankyou for the pretty clear and nice post.
    The best and most important part is (IMHO) the one related to the performance comparision, a thing that unfortunately too many bloggers leave uncovered in their coding-posts.
    I hope to see more examples like this in the future 🙂

  3. I think a more relevant comparison would be late-bound vs manual reflection, rather than late-bound vs early-bound. Of course the late-bound calls are going to be slower than early-bound.

    That said, thank you for the writeup – it was very clear and easy to understand. =) I can’t wait!

  4. Wow!
    [quote]<Main>o__SiteContainer0.<>p__Site2[/quote]

    Very maintainable and innovative code… NOT! Keep that dynamic mumbojumbo out of my strongly typed code and create a dynamic library based on IronWhatever.

    Otherwise nice article, thanks.

  5. "var" represents a statically typed variable whose actual type has been inferred by the compiler. "dynamic" represents a dynamically typed variable whose actual type is completely unknown to the compiler, so all the calls it generates against the variable use late-bound dispatch.

  6. I wonder what would happen if I do this:

    dynamic test = new TestClass();
    test.TestMethod1();
    test = new TestClass2();
    test.TestMethod1();

    where both classes have TestMethod1 defined.
    Then, <Main>o__SiteContainer0.<>p__Site1 would be defined after the first call, and would be calling a wrong function after setting the dynamic variable to a different value.
    Of course, I haven’t seen or installed the CTP so I wouldn’t know the exact implications.

    ozone, the difference is that var is still bound at compile time. the line
    var test = new TestClass1();
    is translated into
    TestClass1 test = new TestClass1();
    You can see that using reflector.
    However, dynamic is translated to object, and changes every usage of that variable, as seen in the example given by Justin.

  7. Newsflash! – making something more indirect makes it slower.

    Still, its nice to have actual numbers.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All