Posted on 10/28/2008 10:24:47 AM by Justin Etheredge
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:
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!