6

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

Community
  • 1
  • 1
Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100
  • It's likely because it isn't sure which `GetData` it's actually using at compile time. Because of that `res2` is `dynamic`. The compiler does seem to check that there is at least one `GetData` though. Interesting question. Now because it thinks `res2` is dynamic, it doesn't correctly figure out that `Count()` is an extension method because that sugar candy happens at compile time. – Yuriy Faktorovich Nov 14 '12 at 21:36
  • Why isn't is sure about GetData? There is only one GetData, its argument and return values perfectly statically typed. Why should compiler mess up GetData output? – Vagif Abilov Nov 14 '12 at 21:43
  • 1
    But this is exactly what happens. Only a few days ago Eric Lippert was writing on his blog about the viral effect of dynamic ( http://blogs.msdn.com/b/ericlippert/archive/2012/11/05/dynamic-contagion-part-one.aspx ). Extension methods don´t work on dynamic. Bear in mind that this is essentially v1 of dynamic. Future versions may be more capable. – flq Nov 14 '12 at 21:47
  • I wonder if they will change it to allow extension methods on dynamic. It would be a breaking change. – Yuriy Faktorovich Nov 14 '12 at 21:51
  • Hm, I changed Count() to Count, and still got the same exception. But Count propery is not an extension - it's an IList property inherited from ICollection. – Vagif Abilov Nov 14 '12 at 21:57

1 Answers1

7

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.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • If I'm not mistaken, "extension methods" don't even exist in the compiled IL. That is, they are _indistinguishable_ from an equivalent static method and are simply compile-time sugar. (which is what you said), in which case even _if_ you wanted to perform a brute force hunt for all extension methods, you couldn't determine what was an extension method vs an unrelated static method. (or worse, what if there were more than one `Count()` static (extension or not) methods that fit the signature? Which one gets used?) – Chris Sinclair Nov 14 '12 at 22:00
  • Dan, thank you for the answer. It makes sense, but what I find really strange now is that if I change Count() to Count the program still raises the same exception. – Vagif Abilov Nov 14 '12 at 22:02
  • @Chris Sinclair, actually, it uses a System.Runtime.CompilerServices.ExtensionAttribute to mark the methods. Otherwise you couldn't use extension methods from assemblies that didn't have source code. – Dan Bryant Nov 14 '12 at 22:03
  • @DanBryant Ahh, didn't realize that. So I guess you could perform a fairly _exhaustive_ search. Maybe that's best left for more heavy runtime compilers (like FLEE or NCalc, which I doubt support it anyway) Thanks for the tip (maybe I'll deviously use it in the future) – Chris Sinclair Nov 14 '12 at 22:09
  • @DanBryant Here is the blog post which I think clarifies the reasons for not resolving result type: http://blogs.msdn.com/b/ericlippert/archive/2012/10/22/a-method-group-of-one.aspx – Vagif Abilov Nov 14 '12 at 22:15