为什么在尝试使用动态参数调用扩展方法时出现错误 CS1973

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

考虑以下代码:

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();
    }
}

我不明白这背后的逻辑。我明白,扩展方法的 first 参数不能是动态的。我什至会理解,如果我传递了一些 另一个动态,调度将无法工作。但显然是这样。它可以将其他类型映射到动态。但是,当我传递该方法所需的 精确类型 时,调度不起作用?它不能将 dynamic 映射到 dynamic?这对我来说没有意义。

我可以阅读并理解该错误,而且我显然知道解决方法,但是谁能告诉我 为什么 这个错误存在?我看不到的更深层次的问题是什么?


有几个现有问题解释了为什么第一个参数 (string) 在这种情况下不能是动态的 - How to call an extension method of a dynamic type? 显示了将分机调用转换为常规调用的解决方法。确实它适用于我的情况。

还有很多 "what is CS1973" 个问题(https://whosebug.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 (在强类型对象上调用已知扩展,但将 dynamic 作为第二个参数传递)。

但这并不能解释为什么在编译时可以确定单个扩展方法仍然不能使用,这就是这个问题的原因。

因为扩展方法在编译时绑定。它们本质上是由编译器转换为静态方法调用,意思是

var result = test.ExtensionMethod(d);

被编译器转换为

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

any参数为dynamic时,则all绑定延迟到运行-time. 运行-time 绑定器当前不支持扩展方法的绑定,编译器知道,并会生成错误。

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

请记住 dynamic(如 var)不是 类型。它只是意味着“我在编译时不知道这是什么类型 - 我会让动态 运行time 查看 actual 类型并确定要做什么那就用它吧。

所以当你说:

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

d 不是 "a dynamic"。在运行时,d的值将是匿名类型。

动态绑定器可以支持扩展方法吗?可能,但迄今为止 *,增加的价值还不值得设计、实施、测试、发布和支持此类功能的成本(相对于其他可以添加的东西) .如果您认为这对框架来说是一个有价值的补充,请随时在 http://connect.microsoft.com/ 提交建议。

我还要指出,在您的(尽管是人为的)场景中没有必要使用 dynamic。如果您使用 object 而不是 dynamic(并将扩展方法移至静态 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();
}

* 至少在 2011 年,在这种情况下找到调用方法的问题被认为太难了 - 请参阅 Eric Lippert 的回答 - : " ...这意味着为了正确解析动态扩展方法调用,DLR 必须在 运行 时以某种方式知道源代码中的所有命名空间嵌套和使用指令代码。我们没有方便的机制将所有信息编码到调用站点中..."