为什么它不报告实际错误?
不,这就是问题所在;它正在报告实际错误。
让我用一个稍微复杂一点的例子来解释一下。假设你有这个:
class CustomerCollection
{
public IEnumerable<R> Select<R>(Func<Customer, R> projection) {...}
}
....
customers.Select( (Customer c)=>c.FristNmae );
好的,根据 C# 规范,错误是什么?您必须在这里非常仔细地阅读规范。让我们解决它。
我们调用 Select 作为一个函数调用,只有一个参数,没有类型参数。我们在 CustomerCollection 中的 Select 上进行查找,搜索名为 Select 的可调用事物——即诸如委托类型的字段或方法之类的事物。由于我们没有指定类型参数,我们匹配任何泛型方法 Select。我们找到一个并从中构建一个方法组。方法组包含单个元素。
现在必须通过重载解析来分析方法组,首先确定候选集,然后从中确定适用的候选集,并从中确定最佳适用候选,并从中确定最终验证的最佳适用候选。如果这些操作中的任何一个失败,则重载解析必须失败并出现错误。其中哪一个失败了?
我们从构建候选集开始。为了获得候选者,我们必须执行方法类型推断以确定类型参数 R 的值。方法类型推断如何工作?
我们有一个 lambda,它的参数类型都是已知的——形式参数是 Customer。为了确定 R,我们必须将 lambda 的返回类型映射到 R。lambda 的返回类型是什么?
我们假设 c 是 Customer 并尝试分析 lambda 主体。这样做会在 Customer 的上下文中查找 FristNmae,但查找失败。
因此,lambda 返回类型推断失败,并且没有向 R 添加边界。
在分析完所有参数之后,R 没有界限。因此,方法类型推断无法确定 R 的类型。
因此方法类型推断失败。
因此没有方法被添加到候选集中。
因此,候选集为空。
因此,不可能有适用的候选人。
因此,此处正确的错误消息应该是“重载解决方案无法找到最终验证的最佳适用候选,因为候选集为空”。
客户会对该错误消息非常不满。我们在错误报告算法中构建了相当多的启发式算法,试图推断出用户实际上可以采取行动来修复错误的更“基本”错误。我们推理:
实际的错误是候选集是空的。为什么候选集为空?
因为方法组中只有一种方法,类型推断失败。
OK,是不是应该报错“overload resolution failed because method type inference failed”?同样,客户会对此不满意。相反,我们再次问这个问题“为什么方法类型推断失败?”
这也是一个糟糕的错误。为什么边界设置为空?
- 因为我们可以确定 R 的唯一参数是无法推断其返回类型的 lambda。
OK,是不是应该报错“overload resolution failed because lambda return type inference failed to infer a return type”? 同样,客户会对此不满意。相反,我们会问“为什么 lambda 无法推断出返回类型?”
这就是我们实际报告的错误。
所以你看到了我们必须经过的绝对曲折的推理链才能给出你想要的错误信息。我们不能只说出了什么问题——重载决策被赋予了一个空的候选集——我们必须追溯过去以确定重载决策是如何进入那个状态的。
这样做的代码非常复杂;它处理的情况比我刚才介绍的更复杂,包括有 n 个不同的泛型方法并且类型推断由于 m 个不同的原因而失败的情况,我们必须从所有这些情况中找出什么是给出的“最佳”理由用户。回想一下,实际上有十几种不同类型的 Select 和重载解决方案可能因不同的原因或相同的原因而失败。
编译器的错误报告中有用于处理各种重载解析失败的启发式;我描述的只是其中之一。
所以现在让我们看看你的具体情况。真正的错误是什么?
我们有一个方法组,其中有一个方法,Foo。我们可以建立一个候选集吗?
是的。有一个候选人。方法 Foo 是调用的候选方法,因为它提供了所有必需的参数 -- bar -- 并且没有额外的参数。
好的,候选集中只有一个方法。候选集中有适用的成员吗?
不能。 bar 对应的实参无法转换为形参类型,因为 lambda 主体包含错误。
因此适用候选集是空的,因此没有最终验证的最佳适用候选,因此重载决策失败。
那么错误应该是什么?同样,我们不能只说“重载解决方案未能找到最终验证的最佳适用候选人”,因为客户会讨厌我们。我们必须开始挖掘错误信息。为什么重载解析失败?
为什么是空的?
有没有可能的最佳人选?
为什么被拒绝了?
好的,在这一点上,显然处理涉及命名参数的重载解决问题的启发式方法决定我们已经挖掘得足够远,这是我们应该报告的错误。如果我们没有命名参数,那么其他一些启发式方法会问:
为什么论点不可转换?
然后我们报告该错误。
错误启发式方法并不完美;离得很远。巧合的是,我本周正在对“简单”重载解决错误报告启发式进行重架构——就像什么时候说“没有采用 2 个参数的方法”和什么时候说“你想要的方法是私有的” " 以及何时说“没有与该名称对应的参数”,等等;完全有可能您正在调用带有两个参数的方法,没有该名称的具有两个参数的公共方法,有一个是私有的,但其中一个具有不匹配的命名参数。快,我们应该报告什么错误?我们必须做出最好的猜测,有时我们可以做出更好的猜测,但还不够复杂。
即使做到这一点也被证明是一项非常棘手的工作。当我们最终重新构建大型重型启发式方法时——比如如何处理 LINQ 表达式中方法类型推断的失败——我将重新审视您的案例,看看我们是否可以改进启发式方法。
但是由于您收到的错误消息是完全正确的,这不是编译器中的错误;相反,它只是特定情况下错误报告启发式的缺点。