消除在重载调用中作为委托传递的重载方法之间的歧义

本文关键字:重载 歧义 方法 之间 调用 | 更新日期: 2023-09-27 18:06:49

假设我在c#中有这个:

class OverloadTest
{
    void Main()
    {
        CallWithDelegate(SomeOverloadedMethod);
    }
    delegate void SomeDelegateWithoutParameters();
    delegate void SomeDelegateWithParameter(int n);
    void CallWithDelegate(SomeDelegateWithoutParameters del) { }
    void CallWithDelegate(SomeDelegateWithParameter del) { }
    void SomeOverloadedMethod() { }
    void SomeOverloadedMethod(int n) { }
}

当然,这不能编译,因为CallWithDelegate(SomeOverloadedMethod);行是不明确的。

现在,假设只有一个CallWithDelegate(SomeDelegateWithoutParameter del)函数(没有重载)。在这种情况下,不会有歧义,因为,从似乎正在发生的事情来看,编译器可以查看参数类型并从候选列表中丢弃SomeOverloadedMethod(int n)(因为它只能接受SomeDelegateWithoutParameters),因此它进行编译。

我不打算写这样的代码;从编译器编写者的角度来看,这只是出于好奇。关于这个问题我找不到答案,因为很难用语言来表达。

我想知道在c#中是否有任何方法可以消除Main()中给出的示例中的调用歧义,以便它可以编译。如何指定它,使其解析为传递SomeOverloadedMethod()CallWithDelegate(SomeDelegateWithoutParameters del)或传递SomeOverloadedMethod(int n)CallWithDelegate(SomeDelegateWithParameter del) ?

消除在重载调用中作为委托传递的重载方法之间的歧义

有几种方法可以消除方法组重载解析的歧义。

方法1:强制转换方法组

CallWithDelegate((SomeDelegateWithoutParameters)SomeOverloadedMethod);
CallWithDelegate((SomeDelegateWithParameter)SomeOverloadedMethod);

这消除了重载的歧义。这种语法在实际环境中并不常见,但它是有效的(c# 5规范§6.6方法组转换):

与所有其他隐式和显式转换一样,强制转换操作符可用于显式执行方法组转换。

<子>[…]

方法组可能影响重载解析,并参与类型推断。

方法2:显式实例化委托

CallWithDelegate(new SomeDelegateWithoutParameters(SomeOverloadedMethod));
CallWithDelegate(new SomeDelegateWithParameter(SomeOverloadedMethod));

这与前面的方法相同,但没有语法糖。详细信息请参见§7.6.10.5委托创建表达式

形式为new D(E)委托创建表达式的绑定时间处理,其中D委托类型E表达式,包括以下步骤:

  • 如果E是方法组,则委托创建表达式的处理方式与ED的方法组转换(§6.6)相同。

<子>[…]

甚至还有一个与你的问题密切相关的例子:

如上所述,当从方法组创建委托时,委托的正式参数列表和返回类型决定选择哪个重载方法。在示例

delegate double DoubleFunc(double x);
class A
{
    DoubleFunc f = new DoubleFunc(Square);
    static float Square(float x) {
        return x * x;
    }
    static double Square(double x) {
        return x * x;
    }
}

A.f字段用一个引用第二个Square方法的委托初始化,因为该方法完全匹配DoubleFunc的形式参数列表和返回类型。如果第二个Square方法不存在,就会发生编译时错误。

方法3:使用lambda

CallWithDelegate(() => SomeOverloadedMethod());
CallWithDelegate(i => SomeOverloadedMethod(i));
CallWithDelegate((int i) => SomeOverloadedMethod(i)); // Explicit types, if needed

这种形式没有歧义,但它具有间接性(调用lambda,然后调用目标方法)。这可以通过JIT进行优化,而且很可能不会对性能产生明显的影响。

方法4:使用匿名委托
CallWithDelegate(delegate() { SomeOverloadedMethod(); });
CallWithDelegate(delegate(int i) { SomeOverloadedMethod(i); });

这相当于lambda调用,但它使用更笨重(更老)的delegate语法。


如果你想知道确切的重载解析规则,它们在规范§7.5.3重载解析中有描述。

虽然我不是编译器专家,但我可以告诉你,因为你提供了两个重载的方法来匹配这两个方法,编译器没有办法识别你的实际意图。它不能编译,因为在编译时目前没有实际的识别信息,正如Lucas所提到的,你可以强制转换来消除歧义。对于编译器来说,要解决这个问题,它需要运行时信息,根据您可能尝试传递的参数,您实际想要使用哪个方法。