重载的方法组参数会混淆重载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是错误的:它返回一个二元组,因此不能转换成相关的Func
types。
顺便说一句,下面的任何一个让编译器都很高兴:
- 为方法组参数指定一个types参数:
Tuple.Create<char>
(也许这实际上是一个types推断问题?)。 - 使参数成为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)
更好也不差,所以编译器不能决定。