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

HLo*_*nzi 9 c# overload-resolution

假设我在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) { }
}
Run Code Online (Sandbox Code Playgroud)

当然,这不会编译,因为该行CallWithDelegate(SomeOverloadedMethod);是模糊的.

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

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

我想知道C#中是否有任何方法可以消除Main()给定示例中的调用,以便编译.如何指定它以便它被解析为CallWithDelegate(SomeDelegateWithoutParameters del)传递SomeOverloadedMethod()或被CallWithDelegate(SomeDelegateWithParameter del)传递SomeOverloadedMethod(int n)

Luc*_*ski 10

有几种方法可以消除方法组的重载决策.

方法1:强制转换方法组

CallWithDelegate((SomeDelegateWithoutParameters)SomeOverloadedMethod);
CallWithDelegate((SomeDelegateWithParameter)SomeOverloadedMethod);
Run Code Online (Sandbox Code Playgroud)

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

与所有其他隐式和显式转换一样,转换运算符可用于显式执行方法组转换.

[...]

方法组可能会影响重载决策,并参与类型推断.

方法2:显式实例化委托

CallWithDelegate(new SomeDelegateWithoutParameters(SomeOverloadedMethod));
CallWithDelegate(new SomeDelegateWithParameter(SomeOverloadedMethod));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 如果E是方法组,则委托创建表达式以相同的方式处理为从方法组转换(6.6节)ED.

[...]

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

方法3:使用lambda

CallWithDelegate(() => SomeOverloadedMethod());
CallWithDelegate(i => SomeOverloadedMethod(i));
CallWithDelegate((int i) => SomeOverloadedMethod(i)); // Explicit types, if needed
Run Code Online (Sandbox Code Playgroud)

这个表单不含糊,但它有一个间接(lambda被调用,然后它调用目标方法).这可能会被JIT优化,但它最有可能不会产生明显的性能影响.

方法4:使用匿名委托

CallWithDelegate(delegate() { SomeOverloadedMethod(); });
CallWithDelegate(delegate(int i) { SomeOverloadedMethod(i); });
Run Code Online (Sandbox Code Playgroud)

这相当于lambda调用,但它使用了较大(和较旧)的delegate语法.


如果您想知道确切的重载决策规则,则在§7.5.3过载分辨率的规范中对它们进行了描述.