gpt4 book ai didi

c# - 编译器歧义调用错误-具有Func <>或Action的匿名方法和方法组

转载 作者:太空宇宙 更新时间:2023-11-03 15:44:03 25 4
gpt4 key购买 nike

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

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

我可以使用匿名方法(或lambda语法)愉快地调用这两个重载,但是如果使用方法组语法,则会出现歧义调用的编译器错误。我可以通过显式转换为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() { }
}

C#7.3更新

根据 0xcde在2019年3月20日(我发布此问题的九年后!)下面的评论,由于 improved overload candidates,此代码从C#7.3开始编译。

最佳答案

首先,我只想说乔恩的答案是正确的。这是该规范中最毛茸茸的部分之一,因此对于Jon来说,首先进入它是一件好事。

其次,让我说这一行:

An implicit conversion exists from a method group to a compatible delegate type



(加上重点)深深地误导和不幸。我将与Mads讨论如何在此处删除“兼容”一词。

造成误导和不幸的原因是因为它看起来像是在呼唤15.2节“代理兼容性”。 15.2节描述了 方法和委托(delegate)类型之间的兼容性关系,但这是 方法组和委托(delegate)类型之间可转换性的问题,这是不同的。

现在我们已经解决了这个问题,我们可以遍历规范的6.6节,看看会得到什么。

要进行重载解析,我们需要首先确定哪些重载是适用的候选对象。如果所有参数都可以隐式转换为形式参数类型,则候选人适用。考虑一下程序的简化版本:
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);
}
}

因此,让我们逐行进行介绍。

An implicit conversion exists from a method group to a compatible delegate type.



我已经讨论过“兼容”一词在这里是不幸的。继续。我们想知道何时对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.



到现在为止还挺好。 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.



这行并没有说什么有趣的事。

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#的怪异规则。离开片刻,这里有一个例子:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));

在表达式树中,增量操作是非法的。但是,lambda仍可转换为表达式树类型,即使曾经使用过转换,这也是一个错误!这里的原理是,我们可能想更改以后在表达式树中可以执行的规则。更改这些规则不应更改类型系统规则。我们想强制您现在使程序变得模棱两可,因此,当我们将来更改表达式树的规则以使其更好时,我们不会在重载解析中引入重大更改。

无论如何,这是这种奇怪规则的另一个例子。出于过载解决的目的,可以存在转换,但实际上是错误的。尽管实际上,这并非我们所处的情况。

继续:

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.



好。因此,我们针对D1在X上进行了重载解析。 D1的形式参数列表为空,因此我们对X()进行重载解析并获得喜悦,我们找到了一种有效的方法“string X()”。同样,D2的形式参数列表为空。同样,我们发现“字符串X()”也是一种在这里也可以使用的方法。

这里的原理是,确定方法组可转换性需要使用重载解析从方法组中选择一个方法,并且 重载解析不考虑返回类型

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的转换

现在,这条线相关吗?

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)。两者都适用。哪个更好? 在规范的任何地方都没有描述这两个可能的转换之间的更好之处。

现在,可以肯定地说,有效转换比产生错误的转换要好。在这种情况下,那实际上就是说过载解析确实考虑了返回类型,这是我们要避免的事情。那么问题是哪个原则更好:(1)保持重载解析不考虑返回类型的不变性,或者(2)尝试选择一个我们知道会在我们知道不会起作用的转换?

这是一个判断电话。对于lambda,我们会在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



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

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

关于c# - 编译器歧义调用错误-具有Func <>或Action的匿名方法和方法组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28971161/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com