编译器不明确的调用错误 – 具有Func <>或Action的匿名方法和方法组

我有一个场景,我想使用方法组语法而不是匿名方法(或lambda语法)来调用函数。

该函数有两个重载,一个接受一个Action ,另一个接受一个Func<string>

我可以愉快地使用匿名方法(或lambda语法)调用这两个重载,但是如果使用方法组语法,则会得到Ambiguous调用的编译器错误。 我可以通过显式转换为ActionFunc<string> ,但不要认为这是必要的。

任何人都可以解释为什么明确的演员应该是必需的。

下面的代码示例。

 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() { } } 

首先,让我说Jon的答案是正确的。 这是规范中最有趣的部分之一,对乔恩来说这是非常好的,因为它首先潜入。

其次,让我说这一行:

存在从方法组到兼容委托types的隐式转换

(强调加上)是非常误导和不幸的。 我会和Mads谈一谈在这里删除“兼容”这个词。

这是误导和不幸的原因是因为它看起来像是在呼吁第15.2节“代表兼容性”。 第15.2节描述了方法和委托types之间的兼容性关系,但是这是方法组和委托types可转换性的问题,这是不同的。

现在我们已经完成了,我们可以遍历规范的6.6节,看看我们得到了什么。

要做重载parsing,我们需要首先确定哪些重载是适用的候选 。 如果所有参数都可以隐式地转换成forms参数types,那么候选者是适用的。 考虑一下你的程序的简化版本:

 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); } } 

所以让我们一行一行地去看看。

存在从方法组到兼容委托types的隐式转换。

我已经讨论过“兼容”这个词在这里是不是很不幸。 继续。 我们想知道在Y(X)上做重载分辨率时,方法组X是否转换为D1? 它是否转换为D2?

给定一个委托typesD和一个被分类为方法组的expression式E,如果E包含至less一个适用于通过使用该参数构造的参数列表的方法,则存在从E到D的隐式转换D的types和修饰符,如下所述。

到现在为止还挺好。 X可能包含一个适用于D1或D2参数列表的方法。

下面描述从方法组E到委托typesD的编译时应用程序。

这条线真的没有说什么有趣的。

请注意,从E到D的隐式转换的存在并不能保证转换的编译时应用程序无错误地成功。

这条线很有趣。 这意味着存在隐含的转换,但是这些转换可能会变成错误! 这是C#的一个奇怪的规则。 为了离题一下,这里是一个例子:

 void Q(Expression<Func<string>> f){} string M(int x) { ... } ... int y = 123; Q(()=>M(y++)); 

expression式树中的增量操作是非法的。 但是,lambda仍然可以转换为expression式树型,即使转换曾经被使用过,也是一个错误! 这里的原则是,我们可能想要改变expression式树中可以进入的规则; 改变这些规则不应该改变types系统规则 。 我们现在要迫使你的程序变得毫不含糊,所以当我们改变expression式树的规则以使它们变得更好的时候, 我们不会在重载分辨率上引入重大改变

无论如何,这是这种奇怪的规则的另一个例子。 转换可以存在用于重载分辨率的目的,但实际使用时可能是错误的。 虽然事实上,这不完全是我们在这里的情况。

继续:

(A)的方法调用中select单个方法M […]参数列表A是expression式列表,每个expression式被分类为正式的相应参数的variablesD.参数表

好。 所以我们在D1上做X的重载分辨率。 D1的forms参数列表是空的,所以我们在X()和喜悦上做了重载parsing,我们find了一个可以工作的方法“string X()”。 类似地,D2的forms参数列表是空的。 再次,我们发现“stringX()”也是一种在这里工作的方法。

这里的原则是确定方法组可转换性要求使用重载决策从方法组中select一个方法,重载决策 不考虑返回types

如果algorithm产生错误,则会发生编译时错误。 否则algorithm产生具有与D相同的参数数量的单个最佳方法M,并且转换被认为存在。

方法组X中只有一个方法,所以它必须是最好的。 我们已经成功地certificate了从X到D1以及从X到D2的转换。

现在,这一行是相关的?

所选方法M必须与委托typesD兼容,否则会发生编译时错误。

其实不,不在这个程序里。 我们永远不会激活这条线。 因为,请记住,我们在这里做的是试图在Y(X)上做重载分辨率。 我们有两个候选人Y(D1)和Y(D2)。 两者都适用。 哪个更好在规范中没有任何地方描述这两种可能的转换之间的更好

现在可以肯定的是,一个有效的转换比一个产生错误的转换要好。 那么在这种情况下,那么有效地说重载决议会考虑返回types,这是我们想要避免的。 那么问题是哪个原则更好:(1)保持重载决议不考虑返回types的不变性,或者(2)尝试select一个我们知道将会在我们不知道的转换上运行的转换?

这是一个判断呼吁。 对于lambdas ,我们确实在这种转换中考虑了返回types,在第7.4.3.3节中:

E是匿名函数,T1和T2是具有相同参数列表的委托types或expression式树types,在该参数列表的上下文中存在针对E的推断返回typesX,并且以下之一成立:

  • T1有返回typesY1,T2有返回typesY2,从X到Y1的转换比从X到Y2的转换要好

  • T1有一个返回typesY,并且T2返回无效

不幸的是,方法组转换和lambda转换在这方面是不一致的。 但是,我可以忍受它。

无论如何,我们没有“更好的”规则来确定哪个转换更好,X到D1或X到D2。 因此我们在Y(X)的分辨率上给出了一个模糊误差。

编辑:我想我已经知道了。

正如zinglon所说,这是因为即使编译时应用程序失败,也存在从GetStringAction的隐式转换。 这是6.6节的介绍,有一些重点(我的):

隐式转换(第6.1节)从方法组(第7.1节)存在到兼容的委托types中。 给定委托typesD和被分类为方法组的expression式E,如果E包含至less一个以其正常forms(第7.4.3.1节)适用于构造的参数列表的方法,则存在从E到D的隐式转换通过使用D的参数types和修饰符 ,如下所述。

现在,我对第一句话感到困惑 – 谈到转换为兼容的委托types。 Action不是GetString方法组中任何方法的兼容委托,但GetString()方法以其常规forms适用于通过使用D的参数types和修饰符构造的参数列表。请注意,这不会说话关于D的返回types。这就是为什么它会变得困惑……因为它只会在应用转换时检查GetString()的委托兼容性,而不检查其是否存在。

我认为,简单地把重载放在等式之外是有益的,并且看看转换的存在与其适用性之间的这种差别如何performance出来。 这里有一个简短而完整的例子:

 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); } } 

Main编译中的方法调用expression式都不是,但错误信息不同。 下面是IntMethod(GetString)的一个IntMethod(GetString)

Test.cs(12,9):错误CS1502:“Program.IntMethod(int)”的最佳重载方法匹配有一些无效的参数

换句话说,规范的第7.4.3.1节找不到任何适用的函数成员。

这里是ActionMethod(GetString)的错误:

Test.cs(13,22):错误CS0407:'stringProgram.GetString()'具有错误的返回types

这一次,它已经find了它想要调用的方法 – 但是它没有执行所需的转换。 不幸的是,我无法find执行最终检查的规格位 – 看起来可能在7.5.5.1中,但我不能确切地看到在哪里。


旧的答案删除,除了这一点 – 因为我希望埃里克可以阐明这个问题的“为什么”…

还在寻找…同时,如果我们三次说“Eric Lippert”,你认为我们会去拜访(因此也是答案)吗?

FuncAction的重载是类似的(因为它们都是委托)

 string Function() // Func<string> { } void Function() // Action { } 

如果您注意到,编译器不知道要调用哪一个,因为它们只是返回types不同。

ClassWithDelegateMethods中使用Func<string>Action<string> (显然与ActionFunc<string>非常不同)消除了歧义。

ActionFunc<int>之间也存在歧义。

我也得到了这个模糊的错误:

 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; } } 

进一步的实验表明,当通过自身传递方法组时,返回types在确定使用哪个重载时完全忽略。

 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(); } }