问题描述
我有一个场景,我想使用方法组语法而不是匿名方法(或 lambda 语法)来调用函数.
I have a scenario where I want to use method group syntax rather than anonymous methods (or lambda syntax) for calling a function.
该函数有两个重载,一个采用Action
,另一个采用Func
.
The function has two overloads, one that takes an Action
, the other takes a Func<string>
.
我可以使用匿名方法(或 lambda 语法)愉快地调用这两个重载,但如果我使用方法组语法,则会得到 Ambiguous invocation 的编译器错误.我可以通过显式转换为 Action
或 Func<string>
来解决问题,但我认为这没有必要.
I can happily call the two overloads using anonymous methods (or lambda syntax), but get a compiler error of Ambiguous invocation if I use method group syntax. I can workaround by explicit casting to Action
or Func<string>
, but don't think this should be necessary.
谁能解释为什么需要显式转换.
Can anyone explain why the explicit casts should be required.
下面的代码示例.
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 更新
根据 0xcde 在 2019 年 3 月 20 日(我发布此问题九年后!)下面的评论,这由于 改进的重载候选.
C# 7.3 Update
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.
推荐答案
首先,我只想说 Jon 的答案是正确的.这是规范中最毛骨悚然的部分之一,Jon 非常喜欢他先深入了解它.
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.
其次,让我说这一行:
存在从方法组到兼容委托类型
(强调)具有严重的误导性和不幸性.我将与 Mads 讨论如何在此处删除兼容"一词.
(emphasis added) is deeply misleading and unfortunate. I'll have a talk with Mads about getting the word "compatible" removed here.
这是误导和不幸的原因是因为它看起来像是在调用第 15.2 节委托兼容性".15.2节描述了方法和委托类型之间的兼容性关系,但是这是方法组和委托类型的可转换性问题,这是不同的.
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.
现在我们已经解决了这个问题,我们可以浏览规范的第 6.6 节,看看我们得到了什么.
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.
要进行重载解析,我们首先需要确定哪些重载是适用的候选者.如果所有参数都可以隐式转换为形式参数类型,则候选是适用的.考虑一下您的程序的这个简化版本:
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.
我已经讨论过兼容"这个词在这里是多么的不幸.继续.我们想知道在对 Y(X) 进行重载解析时,方法组 X 是否转换为 D1?它会转换为D2吗?
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?
给定一个委托类型 D 和一个表达式 E 被分类为方法组,隐式转换如果 E 包含在至少一种适用于 [...] 的方法通过使用构造的参数列表的参数类型和修饰符D,如下所述.
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.
到目前为止一切顺利.X 可能包含适用于 D1 或 D2 的参数列表的方法.
So far so good. X might contain a method that is applicable with the argument lists of D1 or D2.
下面描述了从方法组 E 到委托类型 D 的转换的编译时应用.
The compile-time application of a conversion from a method group E to a delegate type D is described in the following.
这一行真的没有说什么有趣的东西.
This line really doesn't say anything interesting.
请注意,从 E 到 D 的隐式转换的存在并不能保证转换的编译时应用程序会成功而不会出错.
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.
这条线很吸引人.这意味着存在隐式转换,但可能会变成错误!这是 C# 的一个奇怪规则.暂时离题,这里有一个例子:
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:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
表达式树中的增量操作是非法的.但是,lambda 仍然可转换 为表达式树类型,即使曾经使用过转换,它也是一个错误!这里的原则是我们可能希望稍后更改表达式树中的规则;更改这些规则不应更改类型系统规则.我们希望强制您现在让您的程序明确,这样当我们将来更改表达式树的规则以使其更好时,我们不会在重载解析中引入破坏性更改.
An increment operation is illegal in an expression tree. However, the lambda is still convertible to 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.
无论如何,这是这种奇怪规则的另一个例子.出于重载解决的目的,可以存在转换,但实际使用时会出错.虽然事实上,这并不是我们现在所处的情况.
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.
继续:
选择单个方法 M 对应于 E(A) [...] 形式的方法调用.参数列表 A 是表达式列表,每个表达式都分类为相应参数的变量 [...]在 D 的形式参数列表中.
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.
好的.所以我们在 X 上对 D1 进行重载解析.D1 的形参列表是空的,所以我们对 X() 进行重载解析,joy,我们找到了一个有效的方法string X()".同样,D2 的形参列表为空.同样,我们发现string X()"也是一种在这里也可以使用的方法.
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.
这里的原则是确定方法组可转换性需要使用重载解析从方法组中选择一个方法,而重载解析不考虑返回类型.
如果算法 [...] 产生错误,则会发生编译时错误.否则,该算法会产生一个单一的最佳方法 M,其参数数量与 D 相同,并且转换被认为存在.
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.
方法组X只有一种方法,所以一定是最好的.我们已经成功地证明了存在从 X 到 D1 以及从 X 到 D2 的转换.
There is only one method in the method group X, so it must be the best. We've successfully proven that a conversion exists from X to D1 and from X to D2.
现在,这条线是否相关?
Now, is this line relevant?
所选方法 M 必须与委托类型 D 兼容,否则会发生编译时错误.
The selected method M must be compatible with the delegate type D, or otherwise, a compile-time error occurs.
实际上,不,不在这个程序中.我们从来没有激活这条线.因为,请记住,我们在这里所做的是尝试对 Y(X) 进行重载决策.我们有两个候选 Y(D1) 和 Y(D2).两者都适用.哪个更好?规范中没有任何地方描述这两种可能的转换之间的更好.
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.
现在,人们当然可以争辩说,有效的转换比产生错误的转换要好.在这种情况下,这实际上就是说,重载决议确实考虑了返回类型,这是我们想要避免的.那么问题是哪种原则更好:(1) 保持重载解析不考虑返回类型的不变性,或者 (2) 尝试选择一种我们知道会有效的转换,而不是我们知道不会有效的转换?
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?
这是一个判断电话.使用 lambdas,我们确实在第 7.4.3.3 节中考虑了此类转换中的返回类型:
This is a judgment call. With lambdas, we do consider the return type in these sorts of conversions, in section 7.4.3.3:
E 是一个匿名函数,T1 和 T2是委托类型或表达式树具有相同参数列表的类型,E 存在推断的返回类型 X在该参数列表的上下文中,并满足以下条件之一:
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有一个返回类型Y1,T2有一个返回类型Y2,转换从 X 到 Y1 比从 X 到 Y2 的转换
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有一个返回类型Y,T2是void返回
T1 has a return type Y, and T2 is void returning
很遗憾,方法组转换和 lambda 转换在这方面是不一致的.但是,我可以忍受它.
It is unfortunate that method group conversions and lambda conversions are inconsistent in this respect. However, I can live with it.
无论如何,我们没有更好"规则来确定哪种转换更好,X 到 D1 或 X 到 D2.因此,我们对 Y(X) 的分辨率给出了一个模糊误差.
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).
这篇关于Compiler Ambiguous invocation error - 匿名方法和方法组与 Func<>或行动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!