【问题标题】:What's really happening with new and override under the covers?幕后的 new 和 override 到底发生了什么?
【发布时间】:2009-08-05 19:25:36
【问题描述】:

我已经找到了大量的实际示例,并且了解覆盖或隐藏方法时的实际输出,但我正在寻找一些关于为什么会这样以及为什么 C# 根据规则允许它的幕后信息对于多态性,这是不允许的——至少,就我对多态性的理解而言(这似乎与 Wikipedia/Webopedia 上的标准定义一致)。

Class Base
{
    public virtual void PrintName()
    {
        Console.WriteLine("BaseClass");
    }
}

Class FirstDerived : Base
{
    public override void PrintName()
    {
        Console.WriteLine("FirstDerived");
    }
}

Class SecondDerived : Base
{
    public new void PrintName()
    {
        Console.WriteLine("SecondDerived");
    }
}

使用以下代码:

FirstDerived b = new FirstDerived();
BaseClass a = b;
b.PrintName();
a.PrintName();

我明白了:

首先派生 第一派生

好的,我明白了,有道理。

SecondDerived c = new SecondDerived();
BaseClass a = c;
c.PrintName();
a.PrintName();

我明白了:

SecondDerived 
BaseClass

好的,这也是有道理的,实例 a 看不到 c.PrintName() 所以它使用自己的方法来打印自己的名称,但是我可以使用以下方法将我的实例转换为其真实类型:

((SecondDerived)a).PrintName();

(a as SecondDerived).PrintName();

得到我期望的输出:

SecondDerived

那么幕后发生了什么?这对于多态性意味着什么?有人告诉我这个设施“破坏了多态性”——我猜根据定义,它确实如此。那正确吗?像 C# 这样的“面向对象”语言真的会让你打破 OOP 的核心原则之一吗?

【问题讨论】:

  • 您在声明中遗漏了返回类型。
  • 呸,所以需要智能感知:D

标签: c# oop polymorphism


【解决方案1】:

(这回答了我认为确实是您问题的中心点的“为什么允许它”。它在 IL 方面的工作方式对我来说不太有趣......让我知道你是否想让我进入。基本上这只是指定使用不同类型令牌调用的方法的情况。)

它允许基类在不破坏派生类的情况下发展。

假设 Base 最初没有 PrintName 方法。获得SecondDerived.PrintName 的唯一方法是拥有一个静态类型为SecondDerived 的表达式,然后调用它。您发货了,一切都很好。

现在快进到 Base 引入 PrintName 方法。这可能与SecondDerived.PrintName 具有相同的语义,也可能不同 - 最安全的做法是假设它没有。

Base.PrintName 的任何调用者都知道他们正在调用新方法 - 他们以前不可能调用它。任何以前使用SecondDerived.PrintName的调用者仍然想要使用它——他们不想突然调用Base.PrintName,这可能会做一些完全不同的事情。

困难在于SecondDerived.PrintName 调用者,他们可能会或可能不会意识到这不是 Base.PrintName 的覆盖。他们当然可以从文档中注意到这一点,但这可能并不明显。但是,至少我们没有破坏现有的代码。

SecondDerived 被重新编译时,作者将通过警告知道现在有一个Base.PrintName 类。他们可以通过添加new 修饰符来坚持现有的非虚拟方案,或者使其覆盖Base.PrintName 方法。在他们做出决定之前,他们会不断收到警告。

根据我的经验,OO 理论中通常不会提到版本控制和兼容性,但 C# 旨在避免兼容性噩梦。它并没有完全解决问题,但它做得很好。

【讨论】:

  • +1 在现实世界中深入了解它为什么会这样,它的含义以及如何在现实世界中使用它。很好的答案。
  • 那么“打破多态性”,并不是说打破了多态性本身,而是增强了多态性,使其与现实世界的部署场景兼容?
  • 它,嗯,当你实际上不想要多态行为时避免多态:)
【解决方案2】:

我回答“它是如何工作的”。乔恩回答了“为什么”部分。

virtual 方法的调用与非virtual 方法的调用有些不同。基本上,virtual 方法声明在基类中引入了“虚拟方法槽”。插槽将保存一个指向实际方法定义的指针(内容将指向派生类中的覆盖版本,并且不会创建新插槽)。当编译器为虚拟方法调用生成代码时,它使用callvirt IL 指令,指定要调用的方法槽。 runtime 会将调用分派给适当的方法。另一方面,使用call IL 指令调用非虚拟方法,编译器将在编译时将其静态解析为实际方法(仅在知道变量的编译时类型的情况下) )。 new 修饰符在编译后的代码中没有任何作用。它本质上是告诉 C# 编译器“老兄,闭嘴!我确定我在做正确的事情”并关闭编译器警告。

new 方法(实际上,任何没有override 修饰符的方法)将引入一个完全独立的方法链(新方法槽)。请注意,new 方法本身可以是 virtual。编译器在解析方法链时会查看变量的静态类型,运行时会在该特定链中选择实际方法

【讨论】:

    【解决方案3】:

    根据Wikipedia definition

    面向对象中的类型多态性 编程是一个人的能力 键入 A,以显示和使用 另一种类型,B

    稍后在同一页面上:

    方法覆盖是子类的地方 替换一个或 更多它的父母的方法。两者都不 方法重载 压倒一切的是他们自己 多态的实现。

    SecondDerived 不为 PrintName 提供覆盖这一事实不会影响其出现和用作 Base 的能力。它提供的新方法实现不会在任何将 SecondDerived 实例视为 Base 实例的地方使用;仅当该实例被显式用作 SecondDerived 的实例时才会使用它。

    此外,除了新的隐藏实现之外,SecondClass 实际上还可以显式实现 Base.PrintName,从而提供自己的覆盖,当被视为 Base 时将使用该覆盖。 (不过,Base 必须是一个明确的接口定义,或者必须从一个接口派生才能允许这样做)

    【讨论】:

    • 实际上除了您发布的链接之外至少有一篇维基百科文章:en.wikipedia.org/wiki/Polymorphism_%28computer_science%29,还有webopedia.com/TERM/P/polymorphism.html 所有在线参考都在某一点或另一个将多态性定义为派生类的能力覆盖从其父级继承的行为。因此,术语“多形体”的意思是“许多形状”——它是同一件事,但不同。
    • 使用 new 定义隐藏继承方法的新方法并不会阻止类也提供基方法的覆盖实现。该定义也没有破坏多态性。
    猜你喜欢
    • 2010-11-21
    • 2011-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-12
    • 1970-01-01
    • 2018-03-29
    • 2019-06-05
    相关资源
    最近更新 更多