我已经找到了很多这方面的实际例子,并且在覆盖或隐藏方法时理解实际输出,但是我正在寻找一些有关为什么会出现这种情况的信息以及为什么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的核心原则之一吗?
答案 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)
面向对象的类型多态 编程是一种能力 类型,A,出现和使用类似 另一种类型,B
稍后在同一页面上:
方法覆盖是子类的位置 替换一个或的实现 更多其父母的方法。也不 方法重载或方法 压倒一切都是他们自己 多态的实现。
SecondDerived不为PrintName提供覆盖的事实不会影响其显示和用作Base的能力。它提供的新方法实现不会在SecondDerived的实例被视为Base的实例的任何地方使用;仅当该实例明确用作SecondDerived的实例时才会使用它。
此外,除了新的隐藏实现之外,SecondClass实际上可以显式地实现Base.PrintName,从而提供它自己的覆盖,当被视为Base时将使用它。 (尽管,Base必须是一个明确的接口定义,或者必须派生一个来允许这个)