为什么C#编译器使用无效方法的重载?

Via*_*ukh 34 c# oop

我对以下代码感到困惑

class A
{
    public void Abc(int q)
    {
        Console.Write("A");
    }
}

class B : A
{
    public void Abc(double p)
    {
        Console.Write("B");
    }
}

    ...
    var b = new B();
    b.Abc((int)1);
Run Code Online (Sandbox Code Playgroud)

代码执行的结果是写入控制台的"B".

实际上B类包含两个Abc方法的重载,第一个用于int参数,第二个用于double.为什么编译器对整数参数使用双版本?

小心方法abc(double)不会遮蔽或覆盖方法abc(int)

hat*_*ica 52

由于编译器可以隐式地将int转换为double,因此它选择B.Abc方法.这篇文章由Jon Skeet解释(搜索"隐含"):

方法调用的目标是Child类型的表达式,因此编译器首先查看Child类.那里只有一种方法,它适用(从int到double的隐式转换),这是被选中的方法.编译器根本不考虑Parent方法.

这样做的原因是为了降低脆弱基类问题的风险......

更多来自Eric Lippert

正如标准所说,"如果派生类中的任何方法适用,则基类中的方法不是候选者".

换句话说,重载决策算法首先在类中搜索适用的方法.如果找到一个,则从候选集中删除更深层基类中的所有其他适用方法以进行重载解析.由于Delta.Frob(float)适用,Charlie.Frob(int)甚至从未被视为候选者.只有在最派生类型中找不到适用的候选者时,我们才开始查看其基类.

如果我们使用这个来自A的附加类扩展您的问题中的示例,事情会变得更有趣:

class C : A {
    public void Abc(byte b) {
        Console.Write("C");
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们执行以下代码

int i = 1;
b.Abc((int)1);
b.Abc(i);
c.Abc((int)1);
c.Abc(i);
Run Code Online (Sandbox Code Playgroud)

结果是BBCA.这是因为在B类的情况下,编译器知道它可以隐式地将任何 int转换为double.在C类的情况下,编译器知道它可以将文字int 1转换为一个字节(因为值1适合一个字节),因此使用C的Abc方法.但是,编译器不能将任何旧的int隐式地转换为字节,因此c.Abc(i)不能使用C的Abc方法.在这种情况下,它必须使用父类.

Implicit Numeric Conversions上的这个页面显示了一个紧凑的表,其中数字类型具有对其他数字类型的隐式转换.

  • Jon Skeet在被问到之前回答了这些问题. (12认同)

Rob*_*Rob 12

即使定义B为:您也可以获得相同的功能:

class B : A
{
    public void Abc(object p)
    {
        Console.Write("B");
    }
}
Run Code Online (Sandbox Code Playgroud)

简单来说,这是因为重载解析是通过查看当前类中定义的方法来完成的.如果当前类中有任何合适的方法,它将停止查找.只有在没有合适的匹配时才会查看基类

您可以查看重载分辨率规范以获取详细说明.


Jir*_*ika 7

不同的语言(例如C++,Java或C#)具有截然不同的重载决策规则.在C#中,根据语言规范正确选择了重载.如果您希望选择其他重载,则可以选择.记住这一点:

当派生类打算为继承的方法声明另一个重载时,为了将所有可用的重载视为等权限对等体,它还必须使用基本调用显式覆盖所有继承的重载.

要求这项练习的语言设计好处是什么?

想象一下,您正在使用第三方库(例如,.NET框架)并从其中一个类派生.在某些时候,你引入了一个名为的私有方法Abc(一个新的,唯一的名称,而不是任何东西的重载).两年后你升级了第三方库版本而没有注意到他们还添加了一个方法,可以访问并且很遗憾地调用Abc,除了它在某处有不同的参数类型(因此升级不会提醒您编译时间它的行为略有不同,甚至可能完全不同.你真的希望私人电话的一半Abc被静默地重定向到第三方Abc吗?在Java中,这可能发生.在C#或C++中,这不会发生.

C#方式的优点在于,对于重新分发的库,在严格保持向后兼容性的同时添加功能更容易一些.实际上有两种方式:

  • 您不会在自己的代码中混淆客户的私有方法调用.
  • 您不会通过添加新的唯一命名方法来打破您的客户,尽管在添加您自己的现有方法的重载之前您仍必须三思而后行.

C#方式的缺点在于它在OOP哲学中削减了一个漏洞,即改变方法只改变实现,而不是类的API.