6

这个问题在某种程度上是对相关帖子的说明,我相信下面的示例描述了问题的本质。

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

对 GetData 的第二次调用仅因为 GetData 收到转换为动态的参数而引发异常,这不是很糟糕吗?该方法本身可以使用这样的参数:它将其视为字符串并返回正确的结果。但是结果又被强制转换为动态,结果数据突然无法根据其底层类型进行处理。除非它被显式地转换回静态类型,正如我们在示例的最后几行中看到的那样。

我不明白为什么必须以这种方式实施。它破坏了静态和动态类型之间的互操作性。一旦使用动态,它就会感染调用链的其余部分,可能会导致类似这样的问题。

更新。有人指出 Count() 是扩展方法,不被识别是有道理的。然后我将调用 res2.Count() 更改为 res2.Count(从扩展方法更改为 Ilist 的属性),但程序在同一个地方引发了相同的异常!现在这很奇怪。

更新2。flq 指出了 Eric Lippert 关于这个主题的博客文章,我相信这篇文章为它为什么以这种方式实现提供了充分的理由:http: //blogs.msdn.com/b/ericlippert/archive/2012/10/22/a -method-group-of-one.aspx

4

1 回答 1

7

问题是 Count 是一种扩展方法。

您将如何在运行时定位扩展方法?有关哪些“在范围内”的信息基于正在编译的特定文件中的“使用”语句。但是,这些不包含在运行时编译的代码中。它应该查看所有加载的程序集中的所有可能的扩展方法吗?项目引用但尚未加载的程序集呢?如果您尝试允许动态使用扩展方法,则会出现数量惊人的边界情况。

在这种情况下,正确的解决方案是以非扩展形式调用静态方法:

Enumerable.Count(res2)

或者,既然您知道IList<T>在这种情况下,只需使用 Count 属性:

res2.Count<--- 编辑:这不起作用,因为它是由数组实现时显式实现的接口属性。


再次查看您的问题,我发现真正的问题不是关于扩展方法解析本身,而是为什么它无法确定可能存在单一方法解析并因此静态地知道类型。我将不得不再考虑一下,但我猜这是边界情况的类似问题,特别是当您开始考虑多个重载时。


这是一个在一般情况下可能出现的令人讨厌的边界情况(尽管不直接适用于您的情况,因为您是从 Object 派生的)。

假设您在程序集 A 中有一个类 Base。在程序集 B 中还有一个类 Derived : Base。在 Derived 类中,您有上面的代码,并且您认为 GetData 只有一种可能的解决方案。但是,现在假设发布了新版本的程序集 A,该版本具有受保护的 GetData 方法和不同的签名。您的派生类继承了这个,DLR 尽职尽责地允许动态绑定到这个新方法。突然间,返回类型可能不是您所假设的。请注意,所有这些都可以在您不重新编译程序集 B的情况下发生。这意味着运行前编译器不能假定 DLR 将解析为运行前编译器认为是唯一选择的类型,因为运行时的动态环境可能会产生不同的类型。

于 2012-11-14T21:47:06.857 回答