封面下新的和覆盖真正发生了什么?

时间:2009-08-05 19:25:37

标签: c# oop polymorphism

我已经找到了很多这方面的实际例子,并且在覆盖或隐藏方法时理解实际输出,但是我正在寻找一些有关为什么会出现这种情况的信息以及为什么C#根据规则允许它多态性,这不应该被允许 - 至少,就我对多态性的理解而言(这似乎与维基百科/ 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();

我明白了:

FirstDerived FirstDerived

好的,我知道了,这很有意义。

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的核心原则之一吗?

3 个答案:

答案 0 :(得分:7)

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

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

假设Base最初没有PrintName方法。获得SecondDerived.PrintName的唯一方法是使用静态类型为SecondDerived的表达式,并在其上调用它。您运送产品,一切都很好。

现在快进Base引入PrintName方法。这可能与SecondDerived.PrintName具有相同或不同的语义 - 最安全的假设它不是。{/ p>

Base.PrintName的任何来电者都知道他们正在调用新方法 - 他们之前无法调用它。之前使用SecondDerived.PrintName 的所有来电者仍然希望使用它 - 他们不希望突然调用Base.PrintName,这可能会做一些完全不同的事情。

难度为SecondDerived.PrintName来电者,他们可能会或可能不会感谢此​​覆盖Base.PrintName。他们当然可以从文档中注意到这一点,但可能并不明显。但是,至少我们没有破坏现有的代码。

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

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

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:0)

根据Wikipedia definition

  

面向对象的类型多态   编程是一种能力   类型,A,出现和使用类似   另一种类型,B

稍后在同一页面上:

  

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

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

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