Thursday, October 29, 2009

Scary Halloween-Style C# 4.0 Code

Check out the following code:

using System;
using System.Dynamic;

namespace ConsoleApplication9
{
class Program
{
static void Main(string[] args)
{
dynamic d = new ExpandoObject();
d.Hola = "Howdy";
SaySomething(d);
}

void SaySomething(dynamic d)
{
Console.WriteLine(d.Hola);
}
}
}

What do you think this code should do? There's no static method to accept the incoming parameter, so compile error, right?

Wrong.

This compiles, but blows up at runtime with a RuntimeBinderException, not finding the instance method SaySomething.



While the above compiles just fine, the following does not:

using System;
using System.Dynamic;

namespace ConsoleApplication9
{
class Program
{
static void Main(string[] args)
{
dynamic d = new ExpandoObject();
d.Hola = "Howdy";
SaySomething(d);
}
}
}

Rightly recognizing that SaySomething doesn't exist in the current context.

This is the scariest thing I've seen all week. And I was in Austin last weekend.

So why does it do this? This goes back to the way that overload resolution is achieved during compilation. Eric Lippert wrote about this problem in C# 3.0 earlier this year. Overload resolution steps (for C# 3.0) are as follows, in this order:

1. First, each overload is examined according to it's arguments.
2. Next the determination of whether or not the overload is accessible from the current static or instance context is made.
3. If the overload selected from step 1 is inaccessible, then a compile-time error is thrown.

Now, let's look at the situation I posed above. The major difference here is when this resolution occurs. The first example actually compiles, because the overload resolution is delayed until runtime. The order of operations, however, stays the same.

At runtime, these steps are performed in the following order:

1. First, each overload is examined according to it's arguments.
2. Next the determination of whether or not the overload is accessible from the current static or instance context is made.
3. If the overload selected from step 1 is inaccessible, then a run-time error is thrown.

In other words, the rules are exactly the same as they were in C# 3.0, but the consequences of this mistake while using dynamic are much more severe than they are when not using the dynamic type.

All this to say, the dynamic type is a nice addition to the C# toolbox, but it needs to be used with extreme care.

With great power comes great responsibility.