Tuesday, November 3, 2009

Bitten by the dynamic Keyword and Overload Resolution in C#

I came a cross a very scary piece of code Today on the web, take a look at the following C# 4.0 code:

using System;
using System.Dynamic;

namespace DynamicDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic d = new ExpandoObject();
            d.Shoot = "Booom!";
            DoSomeThing(d);
        }

        void DoSomeThing(dynamic d)
        {
            Console.WriteLine(d.Shoot);
        }
    }
}



Scared!?
How would the previous code work?
Well, you would say that this code wouldn't compile because the static method "Main" is calling an instance method "DoSomething", right?
Wrong! This code will actually compile!! Yes, it will fail at runtime, but let's say why this piece of code behaved so freakingly scary. First let me show you another example:

using System;
using System.Dynamic;

namespace DynamicDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic d = new ExpandoObject();
            d.Shoot = "Booom!";
            DoSomeThing(d);
        }   
    }
}
Unlike the first example, this last example will not compile. All of this is related to how C# resolves method overloads.

If you examined section 7.5.4.1 of the C# 3.0 specification, you will learn about the Overload Resolution mechanism that C# follows, here's the steps in short:

  1. Each overload is examined, and the best overload is selected according to the arguments. 
  2. C# determines whether the overload is accessible from the current instance or static context.
  3. If the overload selected from step 1 is not accessible then a compile-time error is thrown.
This overload resolution is done through compilation, but with dynamic, overload resolution is delayed until runtime, and it happens in the very same way as the above three steps (but at runtime of course).
So, at the first sample, the runtime has found the best overload to be the instance method "DoSomething" and then, determined that the selected target is not accessible for that call --as it's not accessible for static calls-- so the runtime threw a RuntimeBinderException exception.

For the second sample code, with no dynamic, the compiler finds out that "DoSomething" doesn't exist in the current context, so it will generate a compile-time error.

If you want to learn more about Overload Resolution, read the C# Specification.

Do you think this was scary enough, or am I being just too coward?

4 comments:

llewellynfalco said...

I can't help but be equally scared by the lack of tool support. If the compiler doesn't notice the method doesn't exist, how is the tool suppose to create the method for, or rename the correct method, or allow for the appropriate parameters to be altered, or ....

Itay Maman said...

So counterintuitive. The compiler can check that there is at least one method that can accept the call and then leave the overloading resolution to runtime. This may lead to ambiguity error at runtime, which - still - is much more natural than the current semantics.

Galilyou said...

@Itam Maman: Actually the compiler does choose the candidate set of methods at compile time -that's why it didn't compile for the second example. However, the compiler doesn't choose which exact overload to be called, the runtime does. The runtime selects the best overload to be called based solely on the arguments, then it examines if this overload is accessible, if it's not, a runtime error is thrown.

niclasnilsson said...

Even if it certainly is counter-intuitive, I think these kind of things are only scary to developers who are only used to statically typed languages. I remember people having the same fear for polymorphism (you don't know what's being called) that people often have for dynamic languages (you don't know what's being called), and I have had both these fears myself.

You need to be on top of your code no matter what, and realizing that the compiler is just a tiny and fragile piece of the safety net is a good thing. In C# and Java, developers seem to think that if they do rename method and it still compiles, the code must still work, but that is only true if there does not happen to be any reflection code that depends on that very name.

Working in small steps and driving your code with examples/tests takes away fear of any code. Until we formally and automatically can prove a program to be correct, I believe that will continue to be the safest way to develop software.

Just my 2 cents.

Kind regards
Niclas Nilsson

---
http://niclasnilsson.se
http://twitter.com/niclasnilsson
http://factor10.com