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

Disambiguating between overloaded methods passed as delegates in an overloaded call

假设我在 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() 中的调用的歧义,以便它可以编译。您如何指定它,以便它解析为 CallWithDelegate(SomeDelegateWithoutParameters del) 被传递 SomeOverloadedMethod(),或 CallWithDelegate(SomeDelegateWithParameter del) 被传递 SomeOverloadedMethod(int n)

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

方法一:投方法组

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

这消除了重载的歧义。这是一种非常不常见的语法,但它有效(C# 5 规范 §6.6 方法组转换):

As with all other implicit and explicit conversions, the cast operator can be used to explicitly perform a method group conversion.

[...]

Method groups may influence overload resolution, and participate in type inference.

方法 2:显式实例化委托

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

这与没有语法糖的前一种方法相同。有关详细信息,请参阅 §7.6.10.5 委托创建表达式 中的规范。

The binding-time processing of a delegate-creation-expression of the form new D(E), where D is a delegate-type and E is an expression, consists of the following steps:

  • If E is a method group, the delegate creation expression is processed in the same way as a method group conversion (§6.6) from E to D.

[...]

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

As described above, when a delegate is created from a method group, the formal parameter list and return type of the delegate determine which of the overloaded methods to select. In the example

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

the A.f field is initialized with a delegate that refers to the second Square method because that method exactly matches the formal parameter list and return type of DoubleFunc. Had the second Square method not been present, a compile-time error would have occurred.

方法 3:使用 lambda

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

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

方法四:使用匿名委托

CallWithDelegate(delegate() { SomeOverloadedMethod(); });
CallWithDelegate(delegate(int i) { SomeOverloadedMethod(i); });

这等同于 lambda 调用,但它使用更大(和更旧)的 delegate 语法。


如果您想知道确切的重载解析规则,它们在 §7.5.3 重载解析.

的规范中进行了描述

虽然我不是编译器专家,但我可以告诉您,由于您提供了 2 个与这两种方法都匹配的重载方法,因此编译器无法识别您的实际意图。它无法编译,因为在编译时,目前没有卢卡斯现在提到的实际识别信息,您可以强制转换以消除歧义。为了让编译器解决这个问题,它需要 运行 时间信息,根据您可能尝试传入的参数,了解您实际想要使用的方法。