【问题标题】:Why C# compiler use an invalid method's overload?为什么 C# 编译器使用无效方法的重载?
【发布时间】:2016-05-21 14:06:50
【问题描述】:

我一直被下面的代码弄糊涂了

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

代码执行的结果是“B”写入控制台。

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

注意方法 abc(double) 不会影响或覆盖方法 abc(int)

【问题讨论】:

  • 我怀疑你必须说A b = new B()
  • @JoshuaShearer - 不,它不是重复的。这是重载,不是阴影,也不是覆盖。
  • 考虑一下,如果你写了 B 并调用了 b.Abc(),然后 A 的作者出现并添加了它是 Abc()。他们只是通过调用不同的方法来中断你的调用,但他们不知道他们会这样做。另一方面,如果Abc() 的作者不希望发生这种情况,那么他们应该选择一个不同的名称。
  • 选择的方法不是无效的;这是一种适用的方法。假设您删除了基类方法;你希望派生类方法仍然被选中吗?
  • 还要考虑这种情况:只有派生类方法。执行调用的代码开发人员测试了代码,它运行良好,调用派生类方法。现在基类的作者向基类添加了一个方法并且调用代码现在突然开始调用一个完全不同的方法,在未经测试的场景中,被调用者是一个不太具体的类。如果您测试了一个方法调用并确定它可以与派生类 SwissBankAccount 一起使用,那么您不希望 BankAccount 类的作者能够更改该绑定!

标签: c# oop


【解决方案1】:

由于编译器可以将 int 隐式转换为 double,因此它选择 B.Abc 方法。这在this post by Jon Skeet 中有解释(搜索“隐式”):

方法调用的目标是一个 Child 类型的表达式,所以 编译器首先查看 Child 类。只有一种方法 在那里,它是适用的(有一个从 int 到 double) 所以这是被选中的那个。编译器不 完全考虑 Parent 方法。

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

More from Eric Lippert

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

换句话说,重载解析算法从搜索开始 适用方法的类。如果它找到一个然后所有其他 更深基类中的适用方法已从 重载解决方案的候选集。由于 Delta.Frob(float) 是 适用, Charlie.Frob(int) 甚至从未被视为候选人。 只有在派生最多的类型中没有找到适用的候选项时 我们开始研究它的基类。

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

class C : A {
    public void Abc(byte b) {
        Console.Write("C");
    }
}

如果我们执行下面的代码

int i = 1;
b.Abc((int)1);
b.Abc(i);
c.Abc((int)1);
c.Abc(i);

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

This page on Implicit Numeric Conversions 显示了一个紧凑的表格,其中数字类型隐式转换为其他数字类型。

【讨论】:

    【解决方案2】:

    即使将B 定义为:

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

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

    您可以查看Overload resolution spec 以获得详细说明。

    【讨论】:

    • 感谢 C# speck 的链接,记住所有细节并不容易!
    【解决方案3】:

    不同的语言(例如 C++、Java 或 C#)具有截然不同的重载解析规则。在 C# 中,根据语言规范正确选择了重载。如果您想选择另一个重载,您可以选择。记住这一点:

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

    要求这个练习对语言设计有什么好处?

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

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

    • 您永远不会在客户自己的代码中弄乱他们的私有方法调用。
    • 您永远不会通过添加一个新的唯一命名的方法来破坏您的客户,尽管在添加您自己现有方法的重载之前仍然必须三思而后行。

    C# 方式的缺点是它在 OOP 哲学中留下了一个漏洞,即覆盖方法只改变实现,而不改变类的 API。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多