尝试使用动态参数

时间:2018-01-18 15:27:05

标签: c# dynamic extension-methods dispatch

请考虑以下代码:

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

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

我可以阅读并理解错误,我显然知道解决方法,但任何人都可以启发我为什么这个错误存在?什么是我看不到的更深层次的问题?

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

还有很多“CS1973是什么”的问题(https://stackoverflow.com/search?q=CS1973)但是大多数问题都是第一个(this Xxxx)论证为dynamic(听起来很公平) )或者只是显示相同的行为而没有解释为什么像'System.Web.Mvc.HtmlHelper' has no applicable method named 'Partial'一样(在强类型对象上调用已知扩展名,但是将dynamic作为第二个参数传递)。

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

1 个答案:

答案 0 :(得分:5)

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

var result = test.ExtensionMethod(d);

由编译器转换为

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

任何参数为dynamic时,所有绑定都会延迟到运行时。运行时binder目前不支持编译器知道的扩展方法的绑定,并生成错误。

  

我甚至会理解,如果我传递了一些东西而不是另一种动态,那么调度是否会起作用。

请注意,dynamic(如var)不是类型。它只是意味着“我不知道这是什么类型的编译时 - 我会让动态运行时查看实际类型,然后确定它如何处理它。

所以当你说:

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

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

可以动态活页夹支持扩展方法吗?可能,但到目前为止 * ,增值不值得花费(相对于可以添加的其他东西)来设计,实现,测试,运送和支持这样的功能。如果您认为这对框架是一个有价值的补充,那么请随时在http://connect.microsoft.com/提交建议。

我还要注意,在你的(尽管做作的)场景中没有必要使用dynamic。如果您使用object而不是dynamic(并将扩展方法移动到静态类),您将获得完全相同的行为(使用扩展方法支持):

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的回答 - https://stackoverflow.com/a/5313149/477420:“ .. 。这意味着为了正确解析动态扩展方法调用,不知何故DLR必须在运行时知道所有命名空间嵌套和使用指令都在源代码中。我们没有一个方便编码所有信息的机制进入呼叫网站......“