我的问题是由Eric Lippert的this blog post推动的。请考虑以下代码:
using System;
class Program {
class A {}
class B {}
static void M(A x, B y) { Console.WriteLine("M(A, B)"); }
static void Call(Action<A> f) { f(new A()); }
static void Call(Action<B> f) { f(new B()); }
static void Main() { Call(x => Call(y => M(x, y))); }
}
这成功编译并打印M(A, B)
,因为编译器发现lambda表达式中x
和y
的类型应为A
和B
} 分别。现在,为Program.M
添加重载:
using System;
class Program {
class A {}
class B {}
static void M(A x, B y) { Console.WriteLine("M(A, B)"); }
static void M(B x, A y) { Console.WriteLine("M(B, A)"); } // added line
static void Call(Action<A> f) { f(new A()); }
static void Call(Action<B> f) { f(new B()); }
static void Main() { Call(x => Call(y => M(x, y))); }
}
这会产生编译时错误:
错误CS0121:以下方法或属性之间的调用不明确:'Program.Call(Action&lt; Program.A&gt;)'和'Program.Call(Action&lt; Program.B&gt;)'
编译器无法推断x
和y
的类型。可能x
属于A
类型而y
类型为B
,反之亦然,由于完全对称,两者都不是首选。到现在为止还挺好。现在,为Program.M
添加一个重载:
using System;
class Program {
class A {}
class B {}
static void M(A x, B y) { Console.WriteLine("M(A, B)"); }
static void M(B x, A y) { Console.WriteLine("M(B, A)"); }
static void M(B x, B y) { Console.WriteLine("M(B, B)"); } // added line
static void Call(Action<A> f) { f(new A()); }
static void Call(Action<B> f) { f(new B()); }
static void Main() { Call(x => Call(y => M(x, y))); }
}
这成功编译并再次打印M(A, B)
!我可以猜出原因。编译器解析了Program.Call
的重载,试图为类型为x => Call(y => M(x, y))
的{{1}}和类型为x
的{{1}}编译lambda表达式A
。前者成功,而后者由于在尝试推断x
的类型时检测到歧义而失败。因此,编译器得出结论:B
必须是y
类型。
因此,添加更多歧义导致更少的歧义。这很奇怪。而且,这与埃里克在上述帖子中所写的内容不一致:
如果它有多个解决方案,则编译失败并出现歧义错误。
目前的行为有充分的理由吗?是否只是让编译器的生活更轻松?或者它是编译器/规范中的缺陷?
答案 0 :(得分:12)
有趣的场景。让我们考虑编译器如何分析每个。
在你的第一个场景中,唯一的可能性是x是A而y是B.其他一切都会产生错误。
在你的第二个场景中,我们可以得到x是A,y是B,或者x是B,y是A.两个解决方案都有效,我们没有依据优先选择一个,所以程序是不明确的。
现在考虑你的第三种情况。让我们假设x是B.假设x是B,那么y可以是A或B.我们没有理由更喜欢A或B代表y。因此,x是B的程序是不明确的。因此x不能是B;我们的假设一定是错的。
所以x是A,或者程序是错误的。 x可以是A吗?如果是,那么y必须是B.如果x是A,我们推断没有错误,如果x是B,我们推导出一个错误,所以x必须是A.
由此我们可以推断出x是A而y是B。
这很奇怪。
是的。在没有泛型类型推断和lambdas的世界中,重载分辨率已经足够了。对他们来说真的很难。
我怀疑你的困难在于对第三种情况的更好的分析:
这不是它的工作原理。相反,我们将每个可能的类型分配提供给最外层的 lambda,并尝试推断每个的成功或失败。
如果你认为它有点奇怪,那么&#34;秩序很重要&#34; - 在某种意义上,外面的lambda&#34;特权&#34;在内部的lambdas上,当然,我可以看到那个论点。这是回溯算法的本质。
如果它有多个解决方案,则编译失败并出现歧义错误。
那仍然是真的。在你的第一个和第三个场景中,有一个解决方案可以推导出而没有矛盾;在第二个解决方案中有两个解决方案,这是不明确的。
目前的行为有充分的理由吗?
是。我们非常仔细地考虑了这些规则像所有设计决策一样,有一个妥协的过程。
哈哈哈哈哈哈哈哈哈。只是让编译器的生活更轻松吗?
我花了一年多的时间来设计,指定,实施和测试所有这些逻辑,以及来自我的许多同事的大量时间和精力。 Easy并没有进入它的任何部分。
或者它是编译器/规范中的缺陷?
没有。
规范过程的目标是提出一种设计,根据我们在LINQ标准库中看到的各种过载,产生合理的推论。我认为我们实现了这一目标。 &#34;添加过载永远不会导致模糊程序变得非模糊&#34;在任何时候都不是规范过程的目标。