重载方法组参数混淆重载解析? [英] Overloaded method-group argument confuses overload resolution?

查看:133
本文介绍了重载方法组参数混淆重载解析?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面调用重载 Enumerable.Select 方式:

  VAR itemOnlyOneTuples =测试。选择<焦炭,元组LT;焦炭>>(Tuple.Create);

失败,多义性错误(为了清楚起见去命名空间):

 的调用以下方法或属性之间暧昧:
Enumerable.Select<焦炭,元组LT;焦炭>>
           (IEnumerable的<焦炭>,&Func键LT;焦炭,元组LT;焦炭>>)

Enumerable.Select<焦炭,元组LT;焦炭>>
          (IEnumerable的<焦炭>中Func键< CHAR,INT,元组LT;焦炭>>)

我当然可以理解为什么的的指定类型参数明确会导致歧义(两个重载将适用),但我不认为这样做后的一个。

这似乎不够清楚,我这样做的目的是调用一个重载,该方法组参数解析为 Tuple.Create<焦炭>(炭)。第二个重载不应适用,因为没有 Tuple.Create 重载可以转换为预期的​​ Func键< CHAR,INT,元组LT;焦炭>&GT ; 类型。我的猜测的编译器是由 Tuple.Create&LT混淆;焦炭,INT>(CHAR,INT),但它的返回类型是错误的:它返回一个二元组,并因此无法转换为相关的函数功能键入

顺便说一下,以下任一使编译器高兴:


  1. 指定类型参数的方法组参数: Tuple.Create<烧焦> (?也许,这实际上是一个类型推断的问题)

  2. 制作参数的lambda-EX pression,而不是一个方法组: X => Tuple.Create(X)。 (与上选择呼叫类型推理打得很好)。

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

  VAR itemIndexTwoTuples =测试。选择<焦炭,元组LT; CHAR,INT和GT;>(Tuple.Create);

这里有什么确切的问题?


解决方案

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

<一个href=\"http://stackoverflow.com/questions/4573011/why-is-funct-ambiguous-with-funcienumerablet/4574930#4574930\">Why是Func键&LT; T&GT;暧昧与Func键&LT;&IEnumerable的LT; T&GT;&GT ;?


  

这里有什么确切的问题?


托马斯的猜测基本上是正确的。以下是具体细节。

让我们通过这一次的一步。我们有一个调用:

 测试。选择&LT;焦炭,元组LT;焦炭&GT;&GT;(Tuple.Create);

重载必须确定调用选择的意义。有没有方法在字符串或任何基类的字符串选择,因此这必须是一个扩展方法。

有许多可能的扩展方法候选集,因为字符串转换为的IEnumerable&LT;焦炭&GT; 和presumably有一个 System.Linq的; 在那里的某个地方。有匹配模式的很多扩展方法选择的,通用的元数二,需要一个的IEnumerable&LT;焦炭&GT; 当给定的方法类型参数构建了第一个参数。

在特定的,两人在候选人是:

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

现在,我们面临的第一个问题是有考生的适用?也就是说,有没有从每个提供的参数为相应的形参类型的隐式转换?

这是很好的问​​题。显然,第一个参数将是接收器,一个字符串,这将是隐式转换为的IEnumerable&LT;焦炭&GT; 。现在的问题是,是否第二个参数,方法组Tuple.Create,是隐式转换为形参类型 Func键&LT;焦炭,元组LT;焦炭&GT;&GT; ,和 Func键&LT; CHAR,INT,元组LT;焦炭&GT;方式&gt;

当是方法组转换为一个给定的委托类型?的方法组可以转换为委托类型,当重载决议会成功给予相同类型的参数作为委托的形参类型

也就是说,M为转换为 Func键&LT; A,R&GT; 如果在形式的呼叫重载 M(someA)会成功,因为一个前pression类型的'A''someA。

会重载解析成功上调用 Tuple.Create(someChar)?是;重载会选择 Tuple.Create&LT; char的方式&gt;(字符)

会重载解析成功上调用 Tuple.Create(someChar,someInt)?是的,重载解析会选择 Tuple.Create&LT;焦炭,诠释方式&gt;(CHAR,INT)

由于在这两种情况下重载决议会成功,方法组转换为两种委托类型。 的方法之一的返回类型就不会匹配委托的返回类型的事实是无关紧要的;重载不成功或失败的基础上返回类型分析

人们可能会说的从方法组兑换委托类型的应该基于返回类型分析,成功或失败,但是这不是如何指定的语言;语言指定使用重载的测试方法组转换,我认为这是一个合理的选择。

因此​​,我们有两个适用的候选人。有没有什么办法,我们可以决定哪个是的更好的比其他?该规范规定,转换为的更具体的类型的为好;如果你有

 无效M(字符串s){}
无效M(对象o){}
...
M(NULL);

然后重载决议选择字符串的版本,因为字符串比目标更具体。是那些委托类型比其它更具体的一个?号也不是比另一个更具体。 (这是的好转换规则简化;其实还是有很多同分决赛,但他们都不在这里适用)

因此​​,没有任何依据preFER一个比其他。

再次人们可以合理地说是肯定的,有一个基础,即这些转化的人会产生一个代表返回类型失配误差,其中之一不会。再次,虽然,被指定的语言,考虑的,而不是你所选择的转换是否将最终导致一个错误的形参类型之间的关系来思考betterness。

由于没有基础,使以preFER一个比其他,这是一个多义性错误。

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

 无效M(Func键&LT; INT,INT&F)的温度{}
无效M(前pression&LT;&Func键LT; INT,INT&GT;&GT;除息){}
...
M(X =&GT; Q(+ X));

这是不明确的。即使它是非法的有一个前pression树里面++的兑换逻辑不考虑拉姆达的身体是否有里面的东西,会在离pression树非法的。转换逻辑只是使确保类型检查,和他们做。鉴于此,我们没有理由为preFER的M的比其他人,所以这是一个多义性。

您注意,

 测试。选择&LT;焦炭,元组LT;焦炭&GT;&GT;(Tuple.Create&LT;焦炭&GT;);

成功。现在你知道为什么。重载决策必须确定

  Tuple.Create&LT;焦炭&GT;(someChar)

  Tuple.Create&LT;焦炭&GT;(someChar,someInt)

会成功。自从第一个不和第二个不,第二候选是不适用和消除,并且因此不周围成为暧昧​​

您也注意到,

 测试。选择&LT;焦炭,元组LT;焦炭&GT;&GT;(X =&GT; Tuple.Create(X));

是明确的。 LAMBDA转换不要的考虑返回前pression与目标委托的返回类型类型的兼容性。不幸的是,方法组和lambda前pressions使用两个稍有不同的算法来确定可兑换,但我们现在坚持了下来。请记住,方法组转换已在语言比拉姆达转换长得多;如果他们在同一时间被加入,我想他们的规则将已经作出一致的。

The following call to the overloaded Enumerable.Select method:

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

fails with an ambiguity error (namespaces removed for clarity):

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>>)'

I can certainly understand why not specifying the type-arguments explicitly would result in an ambiguity (both the overloads would apply), but I don't see one after doing so.

It appears clear enough to me that the intention is to call the first overload, with the method-group argument resolving to Tuple.Create<char>(char). The second overload should not apply because none of the Tuple.Create overloads can be converted to the expected Func<char,int,Tuple<char>> type. I'm guessing the compiler is confused by Tuple.Create<char, int>(char, int), but its return-type is wrong: it returns a two-tuple, and is hence not convertible to the relevant Func type.

By the way, any of the following makes the compiler happy:

  1. Specifying a type-argument for the method-group argument: Tuple.Create<char> (Perhaps this is actually a type-inference issue?).
  2. Making the argument a lambda-expression instead of a method-group: x => Tuple.Create(x). (Plays well with type-inference on the Select call).

Unsurprisingly, trying to call the other overload of Select in this manner also fails:

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

What's the exact problem here?

解决方案

First off, I note that this is a duplicate of:

Why is Func<T> ambiguous with Func<IEnumerable<T>>?

What's the exact problem here?

Thomas's guess is essentially correct. Here are the exact details.

Let's go through it a step at a time. We have an invocation:

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

Overload resolution must determine the meaning of the call to Select. There is no method "Select" on string or any base class of string, so this must be an extension method.

There are a number of possible extension methods for the candidate set because string is convertible to IEnumerable<char> and presumably there is a using System.Linq; in there somewhere. There are many extension methods that match the pattern "Select, generic arity two, takes an IEnumerable<char> as the first argument when constructed with the given method type arguments".

In particular, two of the candidates are:

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

Now, the first question we face is are the candidates applicable? That is, is there an implicit conversion from each supplied argument to the corresponding formal parameter type?

An excellent question. Clearly the first argument will be the "receiver", a string, and it will be implicitly convertible to IEnumerable<char>. The question now is whether the second argument, the method group "Tuple.Create", is implicitly convertible to formal parameter types Func<char,Tuple<char>>, and Func<char,int, Tuple<char>>.

When is a method group convertible to a given delegate type? A method group is convertible to a delegate type when overload resolution would have succeeded given arguments of the same types as the delegate's formal parameter types.

That is, M is convertible to Func<A, R> if overload resolution on a call of the form M(someA) would have succeeded, given an expression 'someA' of type 'A'.

Would overload resolution have succeeded on a call to Tuple.Create(someChar)? Yes; overload resolution would have chosen Tuple.Create<char>(char).

Would overload resolution have succeeded on a call to Tuple.Create(someChar, someInt)? Yes, overload resolution would have chosen Tuple.Create<char,int>(char, int).

Since in both cases overload resolution would have succeeded, the method group is convertible to both delegate types. 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.

One might reasonably say that convertibility from method groups to delegate types ought to succeed or fail based on return type analysis, but that's not how the language is specified; the language is specified to use overload resolution as the test for method group conversion, and I think that's a reasonable choice.

Therefore we have two applicable candidates. Is there any way that we can decide which is better than the other? The spec states that the conversion to the more specific type is better; if you have

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

then overload resolution chooses the string version because string is more specific than object. Is one of those delegate types more specific than the other? No. Neither is more specific than the other. (This is a simplification of the better-conversion rules; there are actually lots of tiebreakers, but none of them apply here.)

Therefore there is no basis to prefer one over the other.

Again, one could reasonably say that sure, there is a basis, namely, that one of those conversions would produce a delegate return type mismatch error and one of them would not. Again, though, the language is specified to reason about betterness by considering the relationships between the formal parameter types, and not about whether the conversion you've chosen will eventually result in an error.

Since there is no basis upon which to prefer one over the other, this is an ambiguity error.

It is easy to construct similar ambiguity errors. For example:

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

That's ambiguous. Even though it is illegal to have a ++ inside an expression tree, the convertibility logic does not consider whether the body of a lambda has something inside it that would be illegal in an expression tree. The conversion logic just makes sure that the types check out, and they do. Given that, there's no reason to prefer one of the M's over the other, so this is an ambiguity.

You note that

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

succeeds. You now know why. Overload resolution must determine if

Tuple.Create<char>(someChar)

or

Tuple.Create<char>(someChar, someInt)

would succeed. Since the first one does and the second one does not, the second candidate is inapplicable and eliminated, and is therefore not around to become ambiguous.

You also note that

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

is unambiguous. Lambda conversions do take into account the compatibility of the returned expression's type with the target delegate's return type. It is unfortunate that method groups and lambda expressions use two subtly different algorithms for determining convertibility, but we're stuck with it now. Remember, method group conversions have been in the language a lot longer than lambda conversions; had they been added at the same time, I imagine that their rules would have been made consistent.

这篇关于重载方法组参数混淆重载解析?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆