重载的方法组参数会混淆重载决策?

以下调用重载的
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);
这里的确切问题是什么?     
已邀请:
首先,我注意到这是一个副本: 为什么Func&lt; T&gt;与Func&lt; IEnumerable&lt; T&gt;&gt;?不明确   这里的确切问题是什么? 托马斯的猜测基本上是正确的。这是确切的细节。 让我们一步一步地完成它。我们有一个调用:
"test".Select<char, Tuple<char>>(Tuple.Create); 
重载决策必须确定对Select的调用的含义。字符串或字符串的任何基类都没有“Select”方法,因此这必须是扩展方法。 候选集有许多可能的扩展方法,因为字符串可以转换为
IEnumerable<char>
,并且可能在那里有一个
using System.Linq;
。有许多扩展方法匹配模式“Select,generic arity two,当使用给定的方法类型参数构造时,将第14个作为第一个参数”。 特别是,其中两位候选人是:
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(someA)
的调用的重载解析成功,则M可转换为
Func<A, R>
。 在拨打
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转换要长很多;如果他们同时加入,我想他们的规则会保持一致。     
我猜测编译器被
Tuple.Create<char, int>(char, int)
搞糊涂了,但它的返回类型是错误的:它返回一个两元组。 返回类型不是方法签名的一部分,因此在重载解析期间不考虑它;只有在选择过载后才会进行验证。因此,就编译器所知,
Tuple.Create<char, int>(char, int)
是一个有效的候选者,它既不比ѭ3更好也不差,所以编译器无法决定。     

要回复问题请先登录注册