Search code examples
c#dynamicextension-methodsdispatch

Why is there an error CS1973 when trying to invoke extension method with dynamic argument


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.


Solution

  • 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..."