首先,我注意到这是一个重复的问题:
为什么Func<T>与Func<IEnumerable<T>>不明确?
这里的确切问题是什么?
Thomas的猜测基本上是正确的。以下是确切的细节。
让我们逐步进行。我们有一个调用:
"test".Select<char, Tuple<char>>(Tuple.Create);
重载决议需要确定对 Select 的调用的含义。String 类或其任何基类中没有名为“Select”的方法,因此这必须是一个扩展方法。
由于字符串可转换为 IEnumerable<char>
,并且可能在其中有一个 using System.Linq;
,所以候选集中有许多匹配模式“Select,泛型度量为二,使用给定方法类型参数构造时,以 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>>)
现在,我们面临的第一个问题是候选项是否“适用”?也就是说,每个提供的参数是否有从相应的形式参数类型进行隐式转换的方式?
一个很好的问题。显然,第一个参数将是“接收者”,即一个字符串,并且它将隐式转换为IEnumerable 。现在的问题是第二个参数——方法组“Tuple.Create”是否可以隐式转换为形式参数类型Func >和Func >。
何时可以将方法组转换为给定的委托类型?当重载分辨率使用与委托的形式参数类型相同的类型的参数成功时,方法组可转换为委托类型。
也就是说,如果以某种形式调用M(someA)的重载分辨率会成功,则M可转换为Func ,其中'someA'是类型'A'的表达式。
对于调用Tuple.Create(someChar)的情况,重载分辨率是否成功?是的;重载分辨率将选择Tuple.Create (char)。
对于调用Tuple.Create(someChar,someInt)的情况,重载分辨率是否成功?是的,重载分辨率将选择Tuple.Create (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 而不是另一个 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转换更久了;如果它们在同一时间添加,我想他们的规则会被制定得更加一致。