4

Consider the following code:

internal static class Program
{
    public static string ExtensionMethod(this string format, dynamic args)
    {
        return format + args.ToString();
    }

    private static void Main()
    {
        string test = "hello ";
        dynamic d = new { World = "world" };

        // Error CS1973  'string' has no applicable method named 'ExtensionMethod'
        //                but appears to have an extension method by that name. 
        //               Extension methods cannot be dynamically dispatched. 
        //               Consider casting the dynamic arguments or calling
        //               the extension method without the extension method syntax.
        var result = test.ExtensionMethod(d);

        // this syntax works fine
        var result2 = Program.ExtensionMethod(test, d);

        // for whatever reason, this works, too!
        var result3 = test.ExtensionMethod((object)d);

        // even this works...
        var result4 = test.ExtensionMethod(new { World = "world" });

        Console.WriteLine(result);
        Console.ReadLine();
    }
}

I don't understand the logic behind this. I do understand, that the first argument of an extension method cannot be dynamic. And I even would understand if the dispatch would not work if I passed in something but another dynamic. But obviously it does. It can map other types to dynamic. But that the dispatch does not work when I pass the exact type required by the method? That it cannot map dynamic to dynamic? That does not make sense to me.

I can read and understand the error and I obviously know the workarounds, but can anybody enlighten me why this error exists? What would be the deeper problem that I cannot see?


There are several existing questions explaining why first argument (string) in this case can't be dynamic - How to call an extension method of a dynamic type? showing workaround to convert extension call to regular call. Indeed it works in my case.

There are also plenty of "what is CS1973" questions (https://stackoverflow.com/search?q=CS1973) but again most of them deal with first (this Xxxx) argument being dynamic (which is sounds fair to fail) or just show same behavior without explanation why like 'System.Web.Mvc.HtmlHelper' has no applicable method named 'Partial' (calling known extension on strongly typed object but passing dynamic as second argument).

But that does not explain why when single extension method can be determined at compile time still can't be used which is what this question is about.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • https://stackoverflow.com/a/5313149/477420 is the exact explanation you are looking for. Note that linked as duplicate answer missing very important part that D Stanley answer highlights "When any of the parameters are dynamic, then all binding is deferred until run-time." - but it feels like that information was assumed to be well known in 2011... – Alexei Levenkov Jan 18 '18 at 15:37
  • @AlexeiLevenkov I read that post (and it's duplicate) before I posted this question. The duplicate only explains, why the **first** parameter cannot be dynamic. And as I said in the question, I understand that, but it does not answer my question of why the *others* cannot. – nvoigt Jan 18 '18 at 15:43
  • Ok. I'll show your research effort and reopen. – Alexei Levenkov Jan 18 '18 at 15:46
  • @AlexeiLevenkov Thanks :) – nvoigt Jan 18 '18 at 15:52
  • @Servy You tagged it *again*, as the same duplicate we found out **is not a duplicate**. Alexei even was so kind to add it to my question that it's **not** a duplicate. – nvoigt Jan 18 '18 at 16:20
  • @nvoigt But it *is* a duplicate. It gives you the answer to your question. You saying that it's not a duplicate doesn't change the fact that it provides the exact answer to your question. That someone reopened the question just because you wanted *the same answer* posted under your question a second time is unfortunate. – Servy Jan 18 '18 at 16:23
  • @Servy Sorry, but please quote where it gives an answer to my question? The whole question there is **about the first argument** which I understand (and say in my question) is a *huge* can of worms. My question is about the *other* arguments. Where can I find the bold sentence of the answer here over at the supposed duplicate for example? – nvoigt Jan 18 '18 at 16:25
  • @nvoigt The whole answer is the answer to your question, not just a part of it. Nothing about the answers are specific to it being the first argument. Your question is a duplicate because the difference between your question and that question *has nothing to do with the answers*, and so the answers answer both questions. Just like if someone else asked, "Why can't I call extension methods when using `dynamic` when it's a Thursday?" it's still a duplicate, because the differences in the question *have nothing to do with the answer*, and so the answers answer both questions. – Servy Jan 18 '18 at 16:28
  • @Servy I guess we have to agree to disagree here. – nvoigt Jan 18 '18 at 16:29
  • @nvoigt And this is of course all demonstrated by the fact that you accepted an answer *that just repeats the duplicate*, so clearly the duplicate *does* answer your question adequately. If the duplicate didn't answer your question, then the answer posted here wouldn't answer your question. So again, you wanting someone to just re-post the answer under your own question doesn't make your question not a duplicate. In fact, that's exactly why it *is* a duplicate. – Servy Jan 18 '18 at 16:30
  • @Servy If you cannot see the difference between a first and following arguments and why that might make a difference here, I cannot help. The supposed duplicate explains why the *first* argument cannot be dynamic, the answer here adds information why this also goes for the *others*. If this were a normal method I would agree with you, but for extension methods the first parameter is a special case. Again, we will have to agree to disagree. – nvoigt Jan 18 '18 at 16:34
  • @nvoigt What about the answers to the duplicate doesn't apply if the `dynamic` expression is something other than the first argument? You saying that it's a special case and that it's different, when *it changes nothing* and nothing in any of the answers is dependant on it doesn't mean anything. You think it's special, but it's not, as the answer to both the duplicate and it's repetition here tells you. – Servy Jan 18 '18 at 16:35

1 Answers1

7

Because extension methods are bound at compile time. They are essentially converted by the compiler to a static method call, meaning

var result = test.ExtensionMethod(d);

is converted by the compiler to

var result = Program.ExtensionMethod(test, d);

When any of the parameters are dynamic, then all binding is deferred until run-time. The run-time binder does not currently support binding of extension methods, which the compiler knows, and generates an error.

And I even would understand if the dispatch would not work if I passed in something but another dynamic.

Remember that dynamic (like var) is not a type. It just means "I don't know what type this is at compile-time - I'll let the dynamic runtime look at the actual type and determine what do do with it then.

So when you say:

dynamic d = new { World = "world" };

d is not "a dynamic". At runtime, the value of d will be an anonymous type.

Could the dynamic binder support extension methods? Probably, but to date*, the added value has not been worth the cost (relative to other things that could be added) to design, implement, test, ship, and support such a feature. If you feel this would be a valuable addition to the framework, then feel free to submit a suggestion at http://connect.microsoft.com/.

I would also note that there is no need to use dynamic in your (albeit contrived) scenario. You'd get the exact same behavior (with extension method support) if you used object instead of dynamic (and moved the extension method to a static class):

public static string ExtensionMethod(this string format, object args)
{
    return format + args.ToString();
}
private static void Main()
{
    string test = "hello ";
    object d = new { World = "world" };

    // Error CS1973  'string' has no applicable method named 'ExtensionMethod'
    //                but appears to have an extension method by that name. 
    //               Extension methods cannot be dynamically dispatched. 
    //               Consider casting the dynamic arguments or calling
    //               the extension method without the extension method syntax.
    var result = test.ExtensionMethod(d);

    // this syntax works fine
    var result2 = Program.ExtensionMethod(test, d);

    // for whatever reason, this works, too!
    var result3 = test.ExtensionMethod((object)d);

    // even this works...
    var result4 = test.ExtensionMethod(new { World = "world" });

    Console.WriteLine(result);
    Console.ReadLine();
}

* at least in 2011 problem of finding the method to call in this case was considered too hard - see Eric Lippert's answer - https://stackoverflow.com/a/5313149/477420: " ...That means that in order to get a dynamic extension method invocation resolved correctly, somehow the DLR has to know at runtime what all the namespace nestings and using directives were in your source code. We do not have a mechanism handy for encoding all that information into the call site..."

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • You might be onto something with your `object` suggestion... technically I don't need this to be `dynamic` I just thought it might be the right signal for users to throw in anonymous objects, dictionaries or JObjects. But maybe a comment in the XML doc will suffice. – nvoigt Jan 18 '18 at 15:54
  • D Stanley - please check if you are ok with my editing in link to other question and consider making "When any of the parameters are dynamic, then all binding is deferred until run-time. " more prominent in the answer as this is part most people don't know and it almost instantly explains the behavior. – Alexei Levenkov Jan 18 '18 at 16:08
  • @AlexeiLevenkov I'm good with the edits and am fine merging if that's the better end result. – D Stanley Jan 18 '18 at 16:12
  • 1
    It's worse than just having to encode the `usings` into the dynamic binding. With that encoded we could look up all the relevant methods at runtime (which wouldn't be cheap, incidentally) but what those methods are depends upon what other assemblies are also loaded, so the best matching method could be something very different in different applications using the same library. – Jon Hanna Jan 18 '18 at 16:17
  • @JonHanna Good point. I don't think Eric's answer was intended to be comprehensive list of issues with trying to solve finding method at runtime, but rather showing that even some of the issues are already very hard. – Alexei Levenkov Jan 18 '18 at 16:20
  • @AlexeiLevenkov yeah, but in this case the idea of it picking different methods due to unrelated matters of which methods are available at runtime makes me **glad** they never allowed extension methods. – Jon Hanna Jan 18 '18 at 17:18