以下调用重载的Enumerable.Select方法:
var itemOnlyOneTuples = "test".Select<char, Tuple<char>>(Tuple.Create);
失败并出现歧义错误(为清晰起见,删除了名称空间):
The call is ambiguous between the following methods or properties:
'Enumerable.Select<char,Tuple<char>>
(IEnumerable<char>,Func<char,Tuple<char>>)'
and
'Enumerable.Select<char,Tuple<char>>
(IEnumerable<char>, Func<char,int,Tuple<char>>)'
我当然可以理解为什么不明确指定类型参数会导致歧义(两个都会应用重载),但是在这样做后我没有看到 .
对我来说似乎很清楚,目的是调用第一个重载,方法组参数解析为 Tuple.Create<char>(char)
. 第二个重载不应该适用,因为 Tuple.Create
重载都不能转换为预期的 Func<char,int,Tuple<char>>
类型 . 我猜测编译器被 Tuple.Create<char, int>(char, int)
搞糊涂了,但是它的返回类型是错误的:它返回一个二元组,因此不能转换为相关的 Func
类型 .
顺便说一句,以下任何一个使编译器开心:
-
为method-group参数指定一个type-argument:
Tuple.Create<char>
(也许这实际上是一个类型推断问题?) . -
使参数成为lambda表达式而不是方法组:
x => Tuple.Create(x)
. (在Select
电话上播放类型推理很好) .
不出所料,尝试以这种方式调用 Select
的另一个重载也会失败:
var itemIndexTwoTuples = "test".Select<char, Tuple<char, int>>(Tuple.Create);
这里的确切问题是什么?
2 回答
首先,我注意到这是一个副本:
Why is Func<T> ambiguous with Func<IEnumerable<T>>?
托马斯的猜测基本上是正确的 . 这是确切的细节 .
让我们一步一步地完成它 . 我们有一个调用:
重载决策必须确定对Select的调用的含义 . 字符串或字符串的任何基类都没有“Select”方法,因此这必须是扩展方法 .
候选集有许多可能的扩展方法,因为字符串可以转换为
IEnumerable<char>
,并且可能在某处有一个using System.Linq;
. 有许多扩展方法匹配模式“Select,generic arity two,当使用给定的方法类型参数构造时,将IEnumerable<char>
作为第一个参数” .特别是, candidates 中的两个是:
现在,我们面临的第一个问题是候选人 applicable ?也就是说,是否存在从每个提供的参数到相应的形式参数类型的隐式转换?
一个很好的问题 . 显然,第一个参数将是"receiver",一个字符串,它将隐式转换为
IEnumerable<char>
. 现在的问题是第二个参数,即方法组"Tuple.Create",是否可以隐式转换为形式参数类型Func<char,Tuple<char>>
和Func<char,int, Tuple<char>>
.何时方法组可转换为给定的委托类型?给定与委托的形式参数类型相同类型的参数时,如果重载决策成功,则方法组可转换为委托类型 .
也就是说,如果
M(someA)
形式的调用上的重载解析成功,则M可转换为Func<A, R>
,给定'A'类型的表达式'someA' .在调用
Tuple.Create(someChar)
时,重载解析是否成功?是;重载决议会选择Tuple.Create<char>(char)
.在调用
Tuple.Create(someChar, someInt)
时,重载解析是否成功?是的,重载决议会选择Tuple.Create<char,int>(char, int)
.由于在这两种情况下重载决策都会成功,因此方法组可以转换为两种委托类型 . The fact that the return type of one of the methods would not have matched the return type of the delegate is irrelevant; overload resolution does not succeed or fail based on return type analysis .
有人可能会合理地说,从方法组到委托类型的可转换性应该基于返回类型分析成功或失败,但是这是一个合理的选择 .
因此我们有两个适用的候选人 . 有什么方法可以决定哪个比另一个更好?该规范指出转换为更具体的类型更好;如果你有
然后重载决策选择字符串版本,因为字符串比对象更具体 . 这些委托类型之一是否比另一个更具体?不 . 两者都没有比另一个更具体 . (这是更好的转换规则的简化;实际上有许多破坏者,但这些都不适用于此 . )
因此,没有理由偏爱另一个 .
同样,有人可以合理地说,确实存在一个基础,即其中一个转换会产生委托返回类型不匹配错误,其中一个不会 . 但是,再一次,通过考虑形式参数之间的关系,指定语言来推理更好类型,而不是您选择的转换是否最终会导致错误 .
由于没有依据优先于其他的基础,这是一个模糊性错误 .
构造类似的模糊错误很容易 . 例如:
这是模棱两可的 . 尽管在表达式树内部使用是非法的,但是可转换逻辑不会考虑lambda的主体是否在其中具有在表达式树中非法的内容 . 转换逻辑只是确保类型检出,而且确实如此 . 鉴于此,有另外一个,所以这是一个模棱两可的问题 .
你注意到了
成功 . 你现在知道为什么了 . 重载决议必须确定是否
要么
会成功的 . 由于第一个候选者没有,第二个候选者不适用并且被淘汰,因此不会变得模棱两可 .
你也注意到了
毫不含糊 . Lambda转换确实考虑了返回表达式's type with the target delegate'的返回类型的兼容性 . 不幸的是,方法组和lambda表达式使用两种略有不同的算法来确定可转换性,但我们现在仍然坚持使用它 . 请记住,方法组转换的语言比lambda转换要长很多;如果他们同时加入,我想他们的规则会保持一致 .
在重载决策期间考虑的返回类型是否为't part of the method signature, so it isn't;只有在选择过载后才会进行验证 . 因此,就编译器所知,
Tuple.Create<char, int>(char, int)
是一个有效的候选者,它既不比Tuple.Create<char>(char)
更好也不差,所以编译器无法决定 .