Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad interoperability between static and dynamic C#

This question is somewhat an illustration to a related post, I believe the example below describes the essence of the problem.

class Program
{
    public static IList<string> GetData(string arg)
    {
        return new string[] {"a", "b", "c"};
    }

    static void Main(string[] args)
    {
        var arg1 = "abc";
        var res1 = GetData(arg1);
        Console.WriteLine(res1.Count());

        dynamic arg2 = "abc";
        var res2 = GetData(arg2);
        try
        {
            Console.WriteLine(res2.Count());
        }
        catch (RuntimeBinderException)
        {
            Console.WriteLine("Exception when accessing Count method");
        }

        IEnumerable<string> res3 = res2;
        Console.WriteLine(res3.Count());
    }
}

Isn't it bad that the second call to GetData raises exception only because GetData received a parameter cast to dynamic? The method itself is fine with such argument: it treats it as a string and returns correct result. But result is then cast to dynamic again, and suddenly result data can not be treated according to its underlying type. Unless it's explicitly cast back to a static type, as we see in the last lines of the example.

I failed to understand why it had to be implemented this way. It breaks interoperability between static and dynamic types. Once dynamic is used, it kind of infects the rest of the call chain potentially causing problems like this one.

UPDATE. Some people pointed out that Count() is an extension method, and it makes sense that it's not recognized. Then I changed a call res2.Count() to res2.Count (from an extension method to a property of Ilist), but the program raised the same exception in the same place! Now that is strange.

UPDATE2. flq pointed to Eric Lippert's blog posts on this topic, and I believe this post gives sufficient reasoning for why it is implemented this way: http://blogs.msdn.com/b/ericlippert/archive/2012/10/22/a-method-group-of-one.aspx

like image 808
Vagif Abilov Avatar asked Nov 14 '12 21:11

Vagif Abilov


1 Answers

The issue is that Count is an extension method.

How would you locate extension methods at runtime? The information about which are 'in scope' is based on the 'using' statements in the particular file being compiled. These are not included in the compiled code at runtime, however. Should it look at all possible extension methods in all loaded assemblies? What about assemblies that were referenced by the project, but not yet loaded? There are a surprising number of boundary cases that crop up if you try to allow dynamic use of extension methods.

The correct solution in this case is to call the static method in its non-extension form:

Enumerable.Count(res2)

Or, since you know it's IList<T> in this case, just use the Count property:

res2.Count <--- EDIT: This doesn't work because it's an explicitly implemented interface property when implemented by the array.


Looking at your question again, I see the real question is not about the extension method resolution per se, but rather why it can't determine that there is a single method resolution possible and therefore know the type statically. I'll have to ponder that a bit more, but I'm guessing it's a similar question of boundary cases, particularly once you start considering multiple overloads.


Here's one nasty boundary case that could come up in the general case (though not directly applicable to your case, since you derive from Object).

Suppose you have a class Base in assembly A. There is also a class Derived : Base in assembly B. In the Derived class, you have your code from above and you think there is only one possible resolution for GetData. However, now suppose that a new version of assembly A is published that has a protected GetData method with a different signature. Your derived class inherits this and the DLR dutifully allows for dynamic binding to this new method. Suddenly the return type may not be what you assumed. Note that all of this can occur without you recompiling assembly B. This means the pre-runtime compiler can't assume that the DLR will resolve to the type the pre-runtime compiler believes is the only option, since the dynamic environment at runtime could yield a different type.

like image 185
Dan Bryant Avatar answered Oct 20 '22 16:10

Dan Bryant