虚拟和覆盖

Rha*_*gar 1 c# virtual-functions

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

The*_*kis 6

2003 年的一次采访中,Anders Hejlsberg(C# 的首席架构师)解释说,决定使用两个不同的关键字主要是为了解决跨版本更改基本组件的版本问题。用他自己的话(强调我的):

我可以向您展示一个非常真实的版本控制问题,我们现在确实从 Java 的经验中看到了这个问题。每当他们发布新版本的 Java 类库时,就会发生破坏。每当他们在基类中引入一个新方法时,如果派生类中的某个人有一个同名的方法,那么该方法现在是一个覆盖——除非它有不同的返回类型,它不再编译。问题是 Java 和 C++ 没有捕捉到程序员关于虚拟的意图。

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

从版本控制的角度来看,程序员在声明虚拟方法时表明他们的意图很重要。例如,在 C# 中,您必须明确指出您想要的 virtual 的含义。要声明一个新的虚拟方法,您只需将其标记为virtual。但是要覆盖现有的虚拟方法,您必须说override.

因此,C# 没有我之前描述的特定版本控制问题,在该问题中,我们在派生类中已有的基类中引入了一个方法。在你的课堂上,你会声明为foovirtual。现在我们介绍一个新的虚拟foo. 嗯,没关系。现在有两个 virtual foo。有两个 VTBL 插槽。派生foo隐藏了 base foo,但这很好。在编写foo派生foo函数时,基础甚至都不存在,所以隐藏这个新功能并没有什么问题。事情继续按照他们应该的方式运作。

所以,你正在扩展一个类。您向类中添加了一个虚拟方法。在以后的版本中,基类添加了自己的虚方法,其签名与您的虚方法的签名相匹配。这可能是完全偶然发生的,也可能是因为您的方法实际上服务于相关目的(并且您只是在基类设计者之前解决了特定需求)。无论如何,情况是模棱两可的。

由于存在两个不同的关键字 ( virtualand override),C# 编译器可以为您提供不间断的行为(通过隐藏继承的虚方法并将其与您的虚方法分开)并通过此警告提醒您注意该问题:

'Derived.Foo()' 隐藏继承成员 'Base.Foo()'。要使当前成员覆盖该实现,请添加override关键字。否则添加new关键字。

它现在要求您通过明确您的意图来解决这个问题:从现在开始,您是否覆盖已添加的继承方法 ( override)?或者你还在开始你自己的虚方法,现在隐藏了继承的(virtualnew)?


Dre*_*dan 5

这是一个很好的问题。

您可以使用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();
   }
} 
Run Code Online (Sandbox Code Playgroud)

产量:

B.F
B.F
D.F
D.F
Run Code Online (Sandbox Code Playgroud)

因为:

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