为什么编译器不能在这种重载决策情况下告诉更好的转换目标? (协方差)
理解关于重载决策的C#语言规范显然很难,现在我想知道为什么这个简单的情况失败了:
void Method(Func f) { } void Method(Func f) { } void Call() { Method(() => { throw new NotSupportedException(); }); }
这给出了编译时错误CS0121, 以下方法或属性之间的调用是不明确的:后面是我的两个Method
函数成员(重载)。
我所期望的是Func
是一个比Func
更好的转换目标 ,然后应该使用第一个重载。
自.NET 4和C#4(2010)以来,generics委托类型Func
在TResult
一直是协变的,因此从Func
到Func
存在隐式转换,而显然没有隐式转换可以从Func
到Func
。 那么它会使Func
成为更好的转换目标,而重载决策应该选择第一次重载?
我的问题很简单:我在这里错过了C#规范的哪一部分?
增加:这很好用:
void Call() { Method(null); // OK! }
我的问题很简单:我在这里错过了C#规范的哪一部分?
摘要:
- 您在实现中发现了一个未知的小错误。
- 出于向后兼容性原因,将保留该错误。
- C#3规范包含有关如何处理“空”案例的错误; 它是在C#4规范中修复的。
- 您可以使用任何无法推断返回类型的lambda重现错误行为。 例如:
Method(() => null);
细节:
C#5规范说更好的规则是:
-
如果表达式具有类型,则选择从该类型到候选参数类型的更好转换。
-
如果表达式没有类型且不是lambda,请选择转换为更好的类型。
-
如果表达式是lambda,那么首先考虑哪个参数类型更好; 如果两者都不是更好并且委托类型具有相同的参数列表,那么请考虑lambda的推断返回类型与委托的返回类型之间的关系。
所以预期的行为是:首先编译器应检查一个参数类型是否明显优于另一个参数类型,无论参数是否具有类型。 如果这不能解决问题并且参数是lambda, 那么检查转换为参数’委托类型’返回类型的推断返回类型哪个更好。
实现中的错误是实现不这样做 。 相反,在参数是lambda的情况下,它完全跳过类型更好性检查并直接进入推断的返回类型更好性检查,然后由于没有推断的返回类型而失败。
我的意图是为罗斯林解决这个问题。 但是,当我去实现它时,我们发现修复导致一些现实世界的代码停止编译。 (我不记得现实代码是什么,我不再能够访问存在兼容性问题的数据库。)因此,我们决定维护现有的小错误。
我注意到在我在C#4中添加委托方差之前,这个bug基本上是不可能的; 在C#3中,两个不同的委托类型不可能或多或少具体,因此唯一可以应用的规则是lambda规则。 由于C#3中没有可以揭示错误的测试,因此很容易编写。 我的坏,对不起。
我还注意到,当你开始将表达式树类型投入混合时,分析变得更加复杂。 即使Func
优于Func
, Expression
也不能转换为Expression
! 如果关于lambda是否要转移到表达式树或委托,那么更好的算法是不可知的,但它在某种程度上不是。 这些案件变得复杂,我不想在这里强调这一点。
这个小错误是实现规范实际所说内容的重要性的对象课程,而不是您认为的内容 。 如果我在C#3中更加小心以确保代码与规范匹配,则代码在“null”情况下会失败,之后很明显C#3规范是错误的。 并且实现在类型检查之前执行lambda检查,这是一个定时炸弹等待C#4转动时突然变成错误的代码。 无论如何都应该首先进行类型检查。
嗯,你是对的。 这里导致问题的原因是您作为参数传递的委托。 它没有明确的返回类型,你只是抛出exception。 Exception
基本上是一个object
但它不被视为方法的返回类型。 由于exception抛出后没有返回调用,编译器不确定它应该使用什么重载。
试试吧
void Call() { Method(() => { throw new NotSupportedException(); return ""; }); }
现在选择一个重载没有问题,因为传递给返回调用的对象的显式声明类型。 由于exception抛出,返回调用无法访问并不重要,但现在编译器知道它应该使用什么重载。
编辑:
至于传递null的情况,frenkly,我不知道答案。