为什么编译器不能在这种重载决策情况下告诉更好的转换目标? (协方差)

理解关于重载决策的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委托类型FuncTResult一直是协变的,因此从FuncFunc存在隐式转换,而显然没有隐式转换可以从FuncFunc 。 那么它会使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优于FuncExpression>也不能转换为Expression> ! 如果关于lambda是否要转移到表达式树或委托,那么更好的算法是不可知的,但它在某种程度上不是。 这些案件变得复杂,我不想在这里强调这一点。

这个小错误是实现规范实际所说内容的重要性的对象课程,而不是您认为的内容 。 如果我在C#3中更加小心以确保代码与规范匹配,则代码在“null”情况下会失败,之后很明显C#3规范是错误的。 并且实现在类型检查之前执行lambda检查,这是一个定时炸弹等待C#4转动时突然变成错误的代码。 无论如何都应该首先进行类型检查。

嗯,你是对的。 这里导致问题的原因是您作为参数传递的委托。 它没有明确的返回类型,你只是抛出exception。 Exception基本上是一个object但它不被视为方法的返回类型。 由于exception抛出后没有返回调用,编译器不确定它应该使用什么重载。

试试吧

 void Call() { Method(() => { throw new NotSupportedException(); return ""; }); } 

现在选择一个重载没有问题,因为传递给返回调用的对象的显式声明类型。 由于exception抛出,返回调用无法访问并不重要,但现在编译器知道它应该使用什么重载。

编辑:

至于传递null的情况,frenkly,我不知道答案。