编译器不明确的调用错误 – 具有Func <>或Action的匿名方法和方法组
我有一个场景,我想使用方法组语法而不是匿名方法(或lambda语法)来调用函数。
该函数有两个重载,一个接受一个Action
,另一个接受一个Func<string>
。
我可以愉快地使用匿名方法(或lambda语法)调用这两个重载,但是如果使用方法组语法,则会得到Ambiguous调用的编译器错误。 我可以通过显式转换为Action
或Func<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所说,这是因为即使编译时应用程序失败,也存在从GetString
到Action
的隐式转换。 这是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”,你认为我们会去拜访(因此也是答案)吗?
Func
和Action
的重载是类似的(因为它们都是委托)
string Function() // Func<string> { } void Function() // Action { }
如果您注意到,编译器不知道要调用哪一个,因为它们只是返回types不同。
在ClassWithDelegateMethods
中使用Func<string>
和Action<string>
(显然与Action
和Func<string>
非常不同)消除了歧义。
Action
和Func<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(); } }