虚拟和重写

4

我来自一个C++的世界,你可能知道在那里只有一个关键字用于虚方法,即virtual。因此,在C++中,您可以使用相同的virtual关键字(在C#中使用override)在派生类中重新定义虚拟方法。我完全了解多态以及在C#中如何使用“override”和new。然而,我无法在书籍中找到是否有一些概念在创建基类和派生类中的两个不同的虚拟方法关键字,即virtualoverride对应?还是只是为了清晰起见而这样做的?


你的C++知识有点过时了。自从C++11以来,编译器可以通过使用override关键字交叉检查重写虚函数的签名(注意,它不会放置在与virtual相同的位置)。 - Ben Voigt
如果您在每个类的基类或派生类中使用 new,则它们会调用自己的方法。如果您使用 override,则基类和派生类将使用重写的方法(即在派生类中定义的方法)。 - Erkan Demirel
3个回答

9
2003年的一次采访中,C#的首席架构师Anders Hejlsberg解释说,决定使用两个不同的关键字主要是为了解决基础组件在不同版本中发生变化的版本控制问题。用他自己的话来说(重点是我的):
我可以向您展示一个非常真实的版本控制问题,这个问题我们在使用Java时确实遇到过。每当他们发布新版本的Java类库时,就会出现破坏。每当他们在基类中引入一个新方法时,如果派生类中有一个同名方法,那么该方法现在就是一个覆盖——除非它具有不同的返回类型,否则它将无法编译。问题在于Java和C++都无法捕获程序员对虚拟的意图。

当你说“虚拟”时,你可能意味着两种情况之一。如果你没有继承相同签名的方法,则这是一个新的虚拟方法。这是一种意思。否则,它是继承方法的覆盖。那是另一种意思。

从版本控制的角度来看,重要的是程序员在声明方法为virtual时表明他们的意图。例如,在C#中,您必须明确表示您打算使用virtual的哪种含义。要声明一个新的虚拟方法,只需将其标记为virtual即可。但是,要覆盖现有的虚拟方法,您必须使用override命令。

因此,C#没有我之前描述的特定版本控制问题,即我们在基类中引入一个在派生类中已经存在的方法。在您的类中,您将声明foo为virtual。现在我们引入了一个新的虚拟foo。好吧,那很好。现在有两个虚拟foo。有两个VTBL插槽。派生的foo隐藏了基本的foo,但这没关系。当编写派生的foo时,根本没有基本的foo,所以隐藏这个新功能没有任何问题。事情继续按照预期工作。
所以,您正在扩展一个类。您向类中添加了一个虚方法。在后续版本中,基类添加了自己的虚方法,并且其签名与您的虚方法匹配。这可能是完全偶然发生的,也可能是因为您的方法实际上服务于相关目的(并且您只是在基类设计人员之前解决了特定需求)。无论如何,情况都是不明确的。
由于存在两个不同的关键字(virtual和override),C#编译器可以提供非破坏性行为(通过隐藏继承的虚方法并将其与您的虚方法分开保持分离)并且通过此警告引起您的注意:
'Derived.Foo()' hides inherited member 'Base.Foo()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
现在它要求您通过使您的意图明确来解决问题:从现在开始,您是否覆盖已添加的继承方法(override)?还是仍然启动自己的虚方法,现在隐藏了继承的方法(virtual和new)?

非常感谢,这回答了我的问题。 - Rhaegar

7

这是一个好问题。

你可以使用 override 关键字来重写虚方法,因为你可以在派生类中定义第二个与基类虚方法签名相同的 virtual 方法,并且它也可以被重写。

以下是来自 MSDN 的实际示例:

using System;
class A
{
   public virtual void F() { Console.WriteLine("A.F"); }
}
class B: A
{
   public override void F() { Console.WriteLine("B.F"); }
}
class C: B
{
   new public virtual void F() { Console.WriteLine("C.F"); }
}
class D: C
{
   public override void F() { Console.WriteLine("D.F"); }
}
class Test
{
   static void Main() {
      D d = new D();
      A a = d;
      B b = d;
      C c = d;
      a.F();
      b.F();
      c.F();
      d.F();
   }
} 

产出:
B.F
B.F
D.F
D.F

原因是:

C 和 D 类包含两个具有相同签名的虚方法:一个由 A 引入,一个由 C 引入。C 引入的方法隐藏了从 A 继承的方法。因此,D 中的覆盖声明覆盖了由 C 引入的方法,D 无法覆盖由 A 引入的方法。


1
太棒了,这是一个完美的解释。谢谢你。 - Rhaegar

0

为了清晰起见,我假设一些事情。个人而言,我更喜欢在更改父类行为时使用override关键字。此外,我认为这样做可以更清楚地表明C#不允许多重继承,而C++中的一个类可能会同时继承自祖父类和父类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接