C# 编译器不明确的调用错误 - 匿名方法和带有 Func<> 或 Action 的方法组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2057146/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Compiler Ambiguous invocation error - anonymous method and method group with Func<> or Action
提问by Richard Ev
I have a scenario where I want to use method group syntax rather than anonymous methods (or lambda syntax) for calling a function.
我有一个场景,我想使用方法组语法而不是匿名方法(或 lambda 语法)来调用函数。
The function has two overloads, one that takes an Action
, the other takes a Func<string>
.
该函数有两个重载,一个采用Action
,另一个采用Func<string>
。
I can happily call the two overloads using anonymous methods (or lambda syntax), but get a compiler error of Ambiguous invocationif I use method group syntax. I can workaround by explicit casting to Action
or Func<string>
, but don't think this should be necessary.
我可以愉快地使用匿名方法(或 lambda 语法)调用这两个重载,但是如果我使用方法组语法,则会得到Ambiguous 调用的编译器错误。我可以通过显式转换为Action
or来解决Func<string>
,但不要认为这应该是必要的。
Can anyone explain why the explicit casts should be required.
任何人都可以解释为什么需要显式转换。
Code sample below.
下面的代码示例。
class Program
{
static void Main(string[] args)
{
ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();
// These both compile (lambda syntax)
classWithDelegateMethods.Method(() => classWithSimpleMethods.GetString());
classWithDelegateMethods.Method(() => classWithSimpleMethods.DoNothing());
// These also compile (method group with explicit cast)
classWithDelegateMethods.Method((Func<string>)classWithSimpleMethods.GetString);
classWithDelegateMethods.Method((Action)classWithSimpleMethods.DoNothing);
// These both error with "Ambiguous invocation" (method group)
classWithDelegateMethods.Method(classWithSimpleMethods.GetString);
classWithDelegateMethods.Method(classWithSimpleMethods.DoNothing);
}
}
class ClassWithDelegateMethods
{
public void Method(Func<string> func) { /* do something */ }
public void Method(Action action) { /* do something */ }
}
class ClassWithSimpleMethods
{
public string GetString() { return ""; }
public void DoNothing() { }
}
C# 7.3 Update
C# 7.3 更新
As per 0xcde's comment below on March 20 2019 (nine years after I posted this question!), this code compiles as of C# 7.3 thanks to improved overload candidates.
根据0xcde在 2019 年 3 月 20 日(我发布此问题九年后!)的评论,由于改进的重载候选,此代码从 C# 7.3 开始编译。
采纳答案by Eric Lippert
First off, let me just say that Jon's answer is correct. This is one of the hairiest parts of the spec, so good on Jon for diving into it head first.
首先,让我说乔恩的回答是正确的。这是规范中最毛茸茸的部分之一,Jon 很高兴首先深入研究它。
Second, let me say that this line:
其次,让我说这一行:
An implicit conversion exists from a method group to a compatible delegate type
存在从方法组到兼容委托类型的隐式转换
(emphasis added) is deeply misleading and unfortunate. I'll have a talk with Mads about getting the word "compatible" removed here.
(强调)是深深误导和不幸的。我将与 Mads 谈谈在此处删除“兼容”一词。
The reason this is misleading and unfortunate is because it looks like this is calling out to section 15.2, "Delegate compatibility". Section 15.2 described the compatibility relationship between methods and delegate types, but this is a question of convertibility of method groups and delegate types, which is different.
这具有误导性和不幸性的原因是因为它看起来像是在调用第 15.2 节“委托兼容性”。15.2节描述了方法和委托类型之间的兼容性关系,但这是一个方法组和委托类型的可转换问题,这是不同的。
Now that we've got that out of the way, we can walk through section 6.6 of the spec and see what we get.
现在我们已经解决了这个问题,我们可以浏览规范的第 6.6 节,看看我们得到了什么。
To do overload resolution we need to first determine which overloads are applicable candidates. A candidate is applicable if all the arguments are implicitly convertible to the formal parameter types. Consider this simplified version of your program:
要进行重载解析,我们需要首先确定哪些重载是适用的候选。如果所有参数都可以隐式转换为形式参数类型,则候选是适用的。考虑这个程序的简化版本:
class Program
{
delegate void D1();
delegate string D2();
static string X() { return null; }
static void Y(D1 d1) {}
static void Y(D2 d2) {}
static void Main()
{
Y(X);
}
}
So let's go through it line by line.
所以让我们一行一行地浏览它。
An implicit conversion exists from a method group to a compatible delegate type.
存在从方法组到兼容委托类型的隐式转换。
I've already discussed how the word "compatible" is unfortunate here. Moving on. We are wondering when doing overload resolution on Y(X), does method group X convert to D1? Does it convert to D2?
我已经讨论过“兼容”这个词在这里是多么不幸。继续。我们想知道在对 Y(X) 进行重载解析时,方法组 X 是否转换为 D1?它会转换为 D2 吗?
Given a delegate type D and an expression E that is classified as a method group, an implicit conversion exists from E to D if E contains at least one method that is applicable [...] to an argument list constructed by use of the parameter types and modifiers of D, as described in the following.
给定委托类型 D 和归类为方法组的表达式 E,如果 E 包含至少一个适用于 [...] 使用参数构造的参数列表的方法,则存在从 E 到 D 的隐式转换D 的类型和修饰符,如下所述。
So far so good. X might contain a method that is applicable with the argument lists of D1 or D2.
到现在为止还挺好。X 可能包含适用于 D1 或 D2 的参数列表的方法。
The compile-time application of a conversion from a method group E to a delegate type D is described in the following.
下面描述了从方法组 E 到委托类型 D 的转换的编译时应用程序。
This line really doesn't say anything interesting.
这条线真的没有说什么有趣的。
Note that the existence of an implicit conversion from E to D does not guarantee that the compile-time application of the conversion will succeed without error.
请注意,从 E 到 D 的隐式转换的存在并不能保证转换的编译时应用程序会成功而不会出错。
This line is fascinating. It means that there are implicit conversions which exist, but which are subject to being turned into errors! This is a bizarre rule of C#. To digress a moment, here's an example:
这条线很吸引人。这意味着存在隐式转换,但可能会变成错误!这是 C# 的一个奇怪的规则。离题一下,这是一个例子:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
An increment operation is illegal in an expression tree. However, the lambda is still convertibleto the expression tree type, even though if the conversion is ever used, it is an error! The principle here is that we might want to change the rules of what can go in an expression tree later; changing those rules should not change the type system rules. We want to force you to make your programs unambiguous now, so that when we change the rules for expression trees in the future to make them better, we don't introduce breaking changes in overload resolution.
表达式树中的增量操作是非法的。但是,lambda 仍然可以转换为表达式树类型,即使曾经使用过转换也是错误的!这里的原则是我们可能想改变以后可以进入表达式树的规则;更改这些规则不应更改类型系统规则。我们想强制您现在使您的程序明确无误,以便将来我们更改表达式树的规则以使其更好时,我们不会在重载解析中引入重大更改。
Anyway, this is another example of this sort of bizarre rule. A conversion can exist for the purposes of overload resolution, but be an error to actually use. Though in fact, that is not exactly the situation we are in here.
无论如何,这是这种奇怪规则的另一个例子。出于重载解析的目的,可以存在转换,但实际使用时会出错。虽然事实上,这并不完全是我们在这里的情况。
Moving on:
继续:
A single method M is selected corresponding to a method invocation of the form E(A) [...] The argument list A is a list of expressions, each classified as a variable [...] of the corresponding parameter in the formal-parameter-list of D.
选择单个方法 M 对应于形式为 E(A) [...] 的方法调用 参数列表 A 是一个表达式列表,每个表达式分类为形式中相应参数的变量 [...] -D 的参数列表。
OK. So we do overload resolution on X with respect to D1. The formal parameter list of D1 is empty, so we do overload resolution on X() and joy, we find a method "string X()" that works. Similarly, the formal parameter list of D2 is empty. Again, we find that "string X()" is a method that works here too.
好的。因此,我们对 X 相对于 D1 进行重载解析。D1 的形参列表是空的,所以我们对 X() 做重载解析,joy,我们找到了一个有效的方法“string X()”。同理,D2 的形参表为空。同样,我们发现“string X()”也是一种在这里也能工作的方法。
The principle here is that determining method group convertibility requires selecting a method from a method group using overload resolution, and overload resolution does not consider return types.
这里的原则是确定方法组的可转换性需要使用重载决议从方法组中选择一个方法,而重载决议不考虑返回类型。
If the algorithm [...] produces an error, then a compile-time error occurs. Otherwise the algorithm produces a single best method M having the same number of parameters as D and the conversion is considered to exist.
如果算法 [...] 产生错误,则会发生编译时错误。否则,该算法会生成与 D 具有相同数量参数的单个最佳方法 M,并且认为存在转换。
There is only one method in the method group X, so it must be the best. We've successfully proven that a conversion existsfrom X to D1 and from X to D2.
方法组X中只有一种方法,所以它一定是最好的。我们已经成功证明存在从 X 到 D1 以及从 X 到 D2 的转换。
Now, is this line relevant?
现在,这条线是否相关?
The selected method M must be compatible with the delegate type D, or otherwise, a compile-time error occurs.
选择的方法 M 必须与委托类型 D 兼容,否则会发生编译时错误。
Actually, no, not in this program. We never get as far as activating this line. Because, remember, what we're doing here is trying to do overload resolution on Y(X). We have two candidates Y(D1) and Y(D2). Both are applicable. Which is better? Nowhere in the specification do we describe betterness between these two possible conversions.
实际上,不,不在这个程序中。我们永远不会激活这条线。因为,请记住,我们在这里所做的是尝试对 Y(X) 进行重载解析。我们有两个候选者 Y(D1) 和 Y(D2)。两者都适用。哪个更好? 在规范中我们没有描述这两种可能的转换之间的更好之处。
Now, one could certainly argue that a valid conversion is better than one that produces an error. That would then effectively be saying, in this case, that overload resolution DOES consider return types, which is something we want to avoid. The question then is which principle is better: (1) maintain the invariant that overload resolution does not consider return types, or (2) try to pick a conversion we know will work over one we know will not?
现在,人们当然可以争辩说有效的转换比产生错误的转换要好。在这种情况下,这实际上就是说,重载决议确实考虑了返回类型,这是我们想要避免的。那么问题是哪个原则更好:(1)保持重载解析不考虑返回类型的不变性,或者(2)尝试选择一个我们知道的转换,而不是我们知道的转换?
This is a judgment call. With lambdas, we doconsider the return type in these sorts of conversions, in section 7.4.3.3:
这是一个判断电话。使用lambdas,我们在 7.4.3.3 节中确实考虑了这些类型转换中的返回类型:
E is an anonymous function, T1 and T2 are delegate types or expression tree types with identical parameter lists, an inferred return type X exists for E in the context of that parameter list, and one of the following holds:
T1 has a return type Y1, and T2 has a return type Y2, and the conversion from X to Y1 is better than the conversion from X to Y2
T1 has a return type Y, and T2 is void returning
E 是匿名函数,T1 和 T2 是具有相同参数列表的委托类型或表达式树类型,在该参数列表的上下文中存在 E 的推断返回类型 X,并且以下条件之一成立:
T1有返回类型Y1,T2有返回类型Y2,从X到Y1的转换比从X到Y2的转换要好
T1 有一个返回类型 Y,T2 是 void 返回
It is unfortunate that method group conversions and lambda conversions are inconsistent in this respect. However, I can live with it.
不幸的是,方法组转换和 lambda 转换在这方面是不一致的。但是,我可以忍受它。
Anyway, we have no "betterness" rule to determine which conversion is better, X to D1 or X to D2. Therefore we give an ambiguity error on the resolution of Y(X).
无论如何,我们没有“更好”规则来确定哪个转换更好,X 到 D1 或 X 到 D2。因此,我们在 Y(X) 的分辨率上给出了一个模糊误差。
回答by Daniel A. White
The overloading with Func
and Action
is akin (because both of them are delegates) to
与超载Func
和Action
类似于(因为两者都代表)到
string Function() // Func<string>
{
}
void Function() // Action
{
}
If you notice, the compiler does not know which one to call because they only differ by return types.
如果您注意到,编译器不知道要调用哪一个,因为它们仅在返回类型上有所不同。
回答by Jon Skeet
EDIT: I think I've got it.
编辑:我想我已经明白了。
As zinglon says, it's because there's an implicit conversion from GetString
to Action
even though the compile-time application would fail. Here's the introduction to section 6.6, with some emphasis (mine):
正如 zinglon 所说,这是因为即使编译时应用程序会失败,也存在从GetString
到的隐式转换Action
。这是第 6.6 节的介绍,有一些重点(我的):
An implicit conversion (§6.1) exists from a method group (§7.1) to a compatible delegate type. Given a delegate type D and an expression E that is classified as a method group, an implicit conversion exists from E to D if E contains at least one method that is applicable in its normal form (§7.4.3.1) to an argument list constructed by use of the parameter types and modifiers of D, as described in the following.
存在从方法组(第 7.1 节)到兼容委托类型的隐式转换(第 6.1 节)。给定委托类型 D 和归类为方法组的表达式 E,如果 E 包含至少一个适用于其正常形式(第 7.4.3.1 节)到构造的参数列表的方法,则存在从 E 到 D 的隐式转换通过使用 D 的参数类型和修饰符,如下所述。
Now, I was getting confused by the first sentence - which talks about a conversion to a compatible delegate type. Action
is not a compatible delegate for any method in the GetString
method group, but the GetString()
method isapplicable in its normal form to an argument list constructed by use of the parameter types and modifiers of D. Note that this doesn'ttalk about the return type of D. That's why it's getting confused... because it would only check for the delegate compatibility of GetString()
when applyingthe conversion, not checking for its existence.
现在,我被第一句话搞糊涂了——它谈到了向兼容委托类型的转换。Action
不是为任何方法兼容的委托GetString
方法组,但该GetString()
方法是适用于正常的形式使用的参数类型和D.注意调节剂的构造,这个参数列表不谈论的返回类型D. 这就是为什么它会变得混乱......因为它只会检查应用转换GetString()
时的委托兼容性,而不是检查它的存在。
I think it's instructive to leave overloading out of the equation briefly, and see how this difference between a conversion's existenceand its applicabilitycan manifest. Here's a short but complete example:
我认为简要地将重载排除在等式之外,看看转换的存在与其适用性之间的这种差异如何体现是有益的。这是一个简短但完整的示例:
using System;
class Program
{
static void ActionMethod(Action action) {}
static void IntMethod(int x) {}
static string GetString() { return ""; }
static void Main(string[] args)
{
IntMethod(GetString);
ActionMethod(GetString);
}
}
Neither of the method invocation expressions in Main
compiles, but the error messages are different. Here's the one for IntMethod(GetString)
:
Main
编译中的方法调用表达式都没有,但错误消息不同。这是一个IntMethod(GetString)
:
Test.cs(12,9): error CS1502: The best overloaded method match for 'Program.IntMethod(int)' has some invalid arguments
Test.cs(12,9):错误 CS1502:“Program.IntMethod(int)”的最佳重载方法匹配有一些无效参数
In other words, section 7.4.3.1 of the spec can't find any applicable function members.
换句话说,规范的第 7.4.3.1 节找不到任何适用的函数成员。
Now here's the error for ActionMethod(GetString)
:
现在这是错误ActionMethod(GetString)
:
Test.cs(13,22): error CS0407: 'string Program.GetString()' has the wrong return type
Test.cs(13,22): 错误 CS0407: 'string Program.GetString()' 有错误的返回类型
This time it's worked out the method it wants to call - but it's failed to then perform the required conversion. Unfortunately I can't find out the bit of the spec where that final check is performed - it looks like it mightbe in 7.5.5.1, but I can't see exactly where.
这一次它计算出了它想要调用的方法 - 但它未能执行所需的转换。不幸的是,我无法找到执行最终检查的规范的一部分 - 看起来它可能在 7.5.5.1 中,但我看不到确切的位置。
Old answer removed, except for this bit - because I expect Eric could shed light onto the "why" of this question...
旧答案已删除,除了这一点 - 因为我希望埃里克可以阐明这个问题的“为什么”......
Still looking... in the mean time, if we say "Eric Lippert" three times, do you think we'll get a visit (and thus an answer)?
仍在寻找......同时,如果我们说“Eric Lippert”三遍,您认为我们会得到访问(从而得到答案)吗?
回答by Matt Ellen
Using Func<string>
and Action<string>
(obviously very different to Action
and Func<string>
) in the ClassWithDelegateMethods
removes the ambiguity.
在 the中使用Func<string>
and Action<string>
(显然与Action
and非常不同Func<string>
)ClassWithDelegateMethods
可以消除歧义。
The ambiguity also occurs between Action
and Func<int>
.
歧义也发生在Action
和之间Func<int>
。
I also get the ambiguity error with this:
我也得到了这样的歧义错误:
class Program
{
static void Main(string[] args)
{
ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();
classWithDelegateMethods.Method(classWithSimpleMethods.GetOne);
}
}
class ClassWithDelegateMethods
{
public void Method(Func<int> func) { /* do something */ }
public void Method(Func<string> func) { /* do something */ }
}
class ClassWithSimpleMethods
{
public string GetString() { return ""; }
public int GetOne() { return 1; }
}
Further experimentation shows that when passing in a method group by its self, the return type is completely ignored when determining which overload to use.
进一步的实验表明,当自己传入一个方法组时,在确定使用哪个重载时,返回类型会被完全忽略。
class Program
{
static void Main(string[] args)
{
ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();
//The call is ambiguous between the following methods or properties:
//'test.ClassWithDelegateMethods.Method(System.Func<int,int>)'
//and 'test.ClassWithDelegateMethods.Method(test.ClassWithDelegateMethods.aDelegate)'
classWithDelegateMethods.Method(classWithSimpleMethods.GetX);
}
}
class ClassWithDelegateMethods
{
public delegate string aDelegate(int x);
public void Method(Func<int> func) { /* do something */ }
public void Method(Func<string> func) { /* do something */ }
public void Method(Func<int, int> func) { /* do something */ }
public void Method(Func<string, string> func) { /* do something */ }
public void Method(aDelegate ad) { }
}
class ClassWithSimpleMethods
{
public string GetString() { return ""; }
public int GetOne() { return 1; }
public string GetX(int x) { return x.ToString(); }
}