好的,事情就是这样。
简短版本:
- 奇怪的是,歧义错误是正确的。
- C# 4 编译器在正确的歧义错误后也会产生虚假错误。这似乎是编译器中的一个错误。
长版本:
我们有一个重载解决问题。过载分辨率的规定非常明确。
第一步:确定候选集。这很容易。候选人是Foo(Func<IEnumerable<String>>)
and Foo(Func<String>)
.
第二步:确定候选集中的哪些成员适用的。适用成员的每个参数都可转换为每个参数类型。
Is Foo(Func<IEnumerable<String>>)
适用的?嗯,是X
可转换为Func<IEnumerable<String>
?
我们参考规范的第 6.6 节。这部分规范就是我们语言设计者所说的“真的很奇怪”。基本上,它说的是可以存在转换,但使用该转换是错误的。 (我们遇到这种奇怪的情况有充分的理由,主要是为了避免未来的重大变化和避免“先有鸡还是先有蛋”的情况,但在你的例子中,我们因此得到了一些不幸的行为。)
基本上,这里的规则是从 X 到不带参数的委托类型的转换exists如果调用表单时重载解析X()
会成功的。显然这样的调用would成功,因此存在转换。实际上using该转换是一个错误,因为返回类型不匹配,但是重载解析总是忽略返回类型.
所以,存在一个转换X
to Func<IEnumerable<String>
,因此该过载是一个适用的候选者。
显然,出于同样的原因,其他重载也是适用的候选者。
第三步:我们现在有两名合适的候选人。哪一个更好”?
“更好”的就是具有以下特征的那个:更具体类型。如果您有两名合适的候选人,M(Animal)
and M(Giraffe)
我们选择长颈鹿版本,因为长颈鹿比动物更具体。我们知道长颈鹿更具体,因为每只长颈鹿都是动物,但并非每只动物都是长颈鹿。
但就您而言,这两种类型都不比另一种更具体。两种 Func 类型之间没有转换。
因此两者都不是更好,因此重载解析会报告错误。
然后,C# 4 编译器出现了一个看似错误的错误,其错误恢复模式无论如何都会选择其中一个候选者,并报告another错误。我不清楚为什么会发生这种情况。基本上,错误恢复是选择 IEnumerable 重载,然后注意到方法组转换产生了站不住脚的结果;也就是说,该字符串不兼容IEnumerable<String>
.
整个情况是相当不幸的;如果返回类型不匹配,最好说没有方法组到委托的转换。 (或者,产生错误的转换总是比不产生错误的转换更糟糕。)但是,我们现在坚持下去。
有趣的事实:lambda 的转换规则do考虑返回类型。如果你说Foo(()=>X())
然后我们就做正确的事。 lambda 和方法组具有不同的可转换规则这一事实是相当不幸的。
因此,总而言之,在这种情况下,编译器实际上是规范的正确实现,而这种特定场景是一些可以说是不幸的规范选择的意外结果。