首先,我注意到这是重复的:
为什么 Func<T> 与 Func<IEnumerable<T>> 有歧义?
这里的确切问题是什么?
托马斯的猜测基本上是正确的。这是确切的细节。
让我们一步一步来。我们有一个调用:
"test".Select<char, Tuple<char>>(Tuple.Create);
重载解析必须确定调用 Select 的含义。字符串或字符串的任何基类都没有方法“Select”,所以这必须是扩展方法。
候选集有许多可能的扩展方法,因为 string 可以转换为IEnumerable<char>
并且可能using System.Linq;
在某处有 a 。有许多扩展方法与模式“选择,泛型二,在IEnumerable<char>
使用给定的方法类型参数构造时将 a 作为第一个参数”模式匹配。
特别是,其中两名候选人是:
Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,Tuple<char>>)
Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,int,Tuple<char>>)
现在,我们面临的第一个问题是候选人是否适用?也就是说,是否存在从每个提供的参数到相应形式参数类型的隐式转换?
一个很好的问题。显然,第一个参数将是“接收者”,一个字符串,并且可以隐式转换为IEnumerable<char>
. 现在的问题是第二个参数,方法组“Tuple.Create”,是否可以隐式转换为形式参数类型Func<char,Tuple<char>>
,和Func<char,int, Tuple<char>>
。
方法组何时可以转换为给定的委托类型? 当给定与委托的形式参数类型相同类型的参数时,方法组可转换为委托类型。
也就是说,如果给定类型为“A”的表达式“someA”,则 M 可转换为Func<A, R>
对表单调用的重载解析是否成功。M(someA)
调用重载解决方案是否成功Tuple.Create(someChar)
?是的; 重载决议会选择Tuple.Create<char>(char)
.
调用重载解决方案是否成功Tuple.Create(someChar, someInt)
?是的,重载决议会选择Tuple.Create<char,int>(char, int)
.
由于在这两种情况下重载决议都会成功,因此方法组可以转换为两种委托类型。其中一种方法的返回类型与委托的返回类型不匹配这一事实是无关紧要的;根据返回类型分析,重载决议不会成功或失败。
有人可能会合理地说,从方法组到委托类型的转换应该根据返回类型分析成功或失败,但这不是指定语言的方式;该语言被指定为使用重载决议作为方法组转换的测试,我认为这是一个合理的选择。
因此,我们有两个适用的候选人。有什么方法可以让我们决定哪个比另一个更好?规范指出,转换为更具体的类型更好;如果你有
void M(string s) {}
void M(object o) {}
...
M(null);
然后重载决议选择字符串版本,因为字符串比对象更具体。这些委托类型中的一种是否比另一种更具体?不,两者都不比另一个更具体。(这是对更好转换规则的简化;实际上有很多决胜局,但没有一个适用于此。)
因此,没有理由偏爱其中之一。
同样,可以合理地说,肯定有一个基础,即其中一个转换会产生委托返回类型不匹配错误,而其中一个不会。但是,再次说明,该语言被指定为通过考虑形式参数类型之间的关系来推理更好,而不是关于您选择的转换是否最终会导致错误。
由于没有依据可以选择一个优于另一个,这是一个模棱两可的错误。
很容易构造类似的歧义错误。例如:
void M(Func<int, int> f){}
void M(Expression<Func<int, int>> ex) {}
...
M(x=>Q(++x));
这是模棱两可的。即使在表达式树中包含 ++ 是非法的,可转换逻辑也不会考虑 lambda 的主体中是否包含在表达式树中非法的内容。转换逻辑只是确保类型签出,并且确实如此。鉴于此,没有理由更喜欢 M 中的一个而不是另一个,所以这是一个模棱两可的问题。
你注意到
"test".Select<char, Tuple<char>>(Tuple.Create<char>);
成功。你现在知道为什么了。重载解决方案必须确定是否
Tuple.Create<char>(someChar)
或者
Tuple.Create<char>(someChar, someInt)
会成功。由于第一个有,第二个没有,所以第二个候选不适用并被淘汰,因此不会变得模棱两可。
你还注意到
"test".Select<char, Tuple<char>>(x=>Tuple.Create(x));
是明确的。Lambda 转换确实考虑了返回表达式的类型与目标委托的返回类型的兼容性。不幸的是,方法组和 lambda 表达式使用两种略有不同的算法来确定可转换性,但我们现在坚持使用它。请记住,方法组转换在语言中的使用时间比 lambda 转换要长得多。如果同时添加它们,我想它们的规则会保持一致。