重载的方法组参数会混淆重载parsing吗?

以下调用重载的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>>)' 

我当然可以理解,为什么明确指定types参数会导致模糊(两个重载都将适用),但是我没有看到这样做。

对我来说看起来很清楚,意图是调用第一个重载,方法组参数parsing为Tuple.Create<char>(char) 。 第二个重载不应该适用,因为没有一个Tuple.Create重载可以转换为预期的Func<char,int,Tuple<char>>types。 我编译器被Tuple.Create<char, int>(char, int)所困惑,但是它的返回types是错误的:它返回一个二元组,因此不能转换成相关的Functypes。

顺便说一句,下面的任何一个让编译器都很高兴:

  1. 为方法组参数指定一个types参数: Tuple.Create<char> (也许这实际上是一个types推断问题?)。
  2. 使参数成为lambdaexpression式而不是方法组: x => Tuple.Create(x) 。 (在Select呼叫中可以很好地进行types推断)。

不出所料,试图以这种方式调用Select的另一个重载也失败了:

 var itemIndexTwoTuples = "test".Select<char, Tuple<char, int>>(Tuple.Create); 

这里有什么确切的问题?

首先,我注意到这是一个重复的:

为什么Func <T>与Func <IEnumerable <T >>混淆?

这里有什么确切的问题?

托马斯的猜测基本上是正确的。 这里是确切的细节。

我们一次一个脚印地走过去。 我们有一个调用:

 "test".Select<char, Tuple<char>>(Tuple.Create); 

重载parsing必须确定对Select的调用的含义。 没有方法“select”string或任何基类的string,所以这必须是一个扩展方法。

候选集有很多可能的扩展方法,因为string可以转换为IEnumerable<char>并且可能有一个using System.Linq; 在那里的某个地方。 有许多扩展方法匹配模式“Select,generic arity two,当使用给定的方法types参数构造时,将IEnumerable<char>作为第一个参数”。

其中两名候选人特别是:

 Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,Tuple<char>>) Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,int,Tuple<char>>) 

现在,我们面临的第一个问题是候选人是否适用 ? 也就是说,是否有从每个提供的参数到相应的forms参数types的隐式转换?

一个很好的问题。 显然,第一个参数是“接收者”,一个string,它可以隐式转换为IEnumerable<char> 。 现在的问题是,方法组“Tuple.Create”的第二个参数是否可以隐式转换为forms参数typesFunc<char,Tuple<char>>Func<char,int, Tuple<char>>

何时可以转换为给定委托types的方法组? 当重载parsing成功时,方法组可以转换为委托types,给定与委托的forms参数types相同types的参数

也就是说,如果M(someA)forms的调用的重载分辨率已经成功,给定expression式'someA'的types'A',则M可转换为Func<A, R>

重载parsing是否成功的调用Tuple.Create(someChar) ? 是; 重载parsing会selectTuple.Create<char>(char)

重载parsing成功的调用Tuple.Create(someChar, someInt) ? 是的,重载parsing会selectTuple.Create<char,int>(char, int)

由于在这两种情况下重载parsing都会成功,所以方法组可以转换为两种委托types。 其中一个方法的返回types不匹配委托的返回types的事实是不相关的; 根据返回types分析,重载parsing不成功或失败

有人可能会合理地说, 从方法组到委托types的可转换性应该基于返回types分析而成功或失败,但这不是指定语言的方式; 该语言被指定使用重载parsing作为方法组转换的testing,我认为这是一个合理的select。

所以我们有两个适用的候选人。 有什么办法可以决定哪个比另一个更好 ? 规范指出,转换到更具体的types是更好的; 如果你有

 void M(string s) {} void M(object o) {} ... M(null); 

那么重载决议selectstring版本,因为string比对象更具体。 这些委托types之一是比另一个更具体吗? 不,不是比另一个更具体。 (这是更好的转换规则的简化,实际上有很多破解者,但这些都不适用于此)。

因此,没有理由相互倾向。

再次,可以合理地说,有一个基础,即,这些转换之一将产生委托返回types不匹配错误,其中之一不会。 同样,通过考虑forms参数types之间的关系,而不是关于您select的转换最终是否会导致错误,通过指定语言来推理更好。

由于没有理由相互偏好,所以这是一个模棱两可的错误。

构造类似的模糊性错误很容易。 例如:

 void M(Func<int, int> f){} void M(Expression<Func<int, int>> ex) {} ... M(x=>Q(++x)); 

这是模糊的。 尽pipe在expression式树中有一个++是非法的, 但是可转换逻辑并不考虑lambdaexpression式的正文是否在expression式树中具有某些内容,这是非法的 。 转换逻辑只是确保types签出,他们这样做。 鉴于此,没有理由select其中一个M,因此这是一个模糊的问题。

你注意到了

 "test".Select<char, Tuple<char>>(Tuple.Create<char>); 

成功。 你现在知道为什么。 重载parsing必须确定是否

 Tuple.Create<char>(someChar) 

要么

 Tuple.Create<char>(someChar, someInt) 

会成功。 既然第一个和第二个都没有,那么第二个候选人就不适用了,所以也就不在这个范围之内了。

你也注意到了

 "test".Select<char, Tuple<char>>(x=>Tuple.Create(x)); 

是明确的。 Lambda转换确实考虑了返回的expression式types与目标委托的返回types的兼容性。 不幸的是,方法组和lambdaexpression式使用两种细微差别的algorithm来确定可转换性,但我们现在仍然坚持使用它。 请记住,方法组转换的语言比lambda转换的时间要长得多。 如果他们在同一时间被添加,我想他们的规则将会一致。

我猜Tuple.Create<char, int>(char, int)使编译器感到困惑,但是它的返回types是错误的:它返回一个二元组。

返回types不是方法签名的一部分,因此在重载parsing期间不考虑; 只有挑选过载才能进行validation。 因此,据编译器知道, Tuple.Create<char, int>(char, int)是一个有效的候选,它既不比Tuple.Create<char>(char)更好也不差,所以编译器不能决定。