在C#中不能在同一个方法上同时使用virtual和override关键字。

26

显然你不能在override修饰符中使用virtual修饰符。

virtual - 可重写的方法

override - 重写其父类中同名方法的方法

这使我相信,如果我在子类中重写一个方法,如果这个子类有一个子类,那么你就不能再次重写那个方法。

如果在C#中在方法声明中同时放置overridevirtual,则会得到编译错误。

然而,我不明白为什么我下面所做的代码会以它所期望的方式工作。

using System;
public class DrawingObject
{
public virtual void Draw()
{
    Console.WriteLine("Drawing Object");
}
}
public class DrawDemo
{
     public static int Main()
     {
          DrawingObject[] dObj = new DrawingObject[3];


          dObj[0] = new DrawingObject();
           dObj[1] = new Line();
           dObj[2] = new LittleLine();

           foreach (DrawingObject drawObj in dObj)
           {
                drawObj.Draw();
           }
            Console.Read();
           return 0;
       }
  }
 public class Line : DrawingObject
 {
       public override void Draw()
       {// the method above me is in fact virtual because LittleLine overid it?
               Console.WriteLine("I'm a Line.");
       }
  }
  public class LittleLine : Line
  {
       public override void Draw()
       {
              Console.WriteLine("I'm a Little Line.");
         }
    }

以下是输出结果:

绘制对象

我是一条线。

我是一条小线。

因此,在Line中的绘制方法似乎被LittleLine覆盖了。 这段代码实际上没有覆盖它吗,还是编译器做了其他的技巧? 或者我没有理解虚函数和覆盖的上下文?


3
一个 override 方法会自动再次变成可重写 (virtual)。 - H H
3
像其他人所说的那样,sealed override 禁止在派生类中进一步重写该方法。还有一个有趣的细节:abstract override 强制要求派生类重写该方法。你的类可能不提供基类虚拟方法的重写实现,但你可以强制从你继承的类对其进行重写。 - pnvn
感谢您澄清了这个问题! - Frank Visaggio
6个回答

34
您可以将某个方法声明为virtual,但只能这样做一次,但您可以多次override它- 覆盖不是最终的,并且不限制从第一个重写类继承的类。最终执行的方法是最后一个覆盖虚方法的方法。因此,您的代码会按预期运行。
相对于C++或Java,C#在覆盖方面非常冗长。这是为了让程序员指定确切意图:
  • 您使用virtual指定子类可以覆盖的方法。
  • 您使用override指定正在重写您已知为虚拟的方法(如果不是,则编译器将报告错误)。
  • 您使用sealed防止进一步覆盖。
  • 您使用new来隐藏而不是重写。
这可能有点令人困惑,有时很烦人,但它确保您真正知道自己在做什么,并使您的意图自我记录。

那么当调用LittleLine对象的draw方法时,编译器会是怎样的处理呢?它会看到这个对象有一个draw方法然后直接调用吗?还是会先看到littleLine是Line类的基类,再看到Line又是DrawingObject的基类,从根父类的方法开始执行,然后向下依次执行所有相关方法呢? - Frank Visaggio
有没有办法像super.draw()这样做,或者在类本身以外的任何地方调用该对象的父方法?例如执行((Line)dObj[2]).draw()仍然会调用小线条draw的实现。是否有任何方法可以获取您覆盖的方法的父级实现呢? - Frank Visaggio
1
据我所知,@BobSinclar,没有办法从外部调用重写的方法(参见此问题)。虽然使用反射可能会实现这一点,但即使如此,也是最为棘手的。 - Eran
更多的知识,简洁地总结。 :-) 谢谢。 - Asif Mushtaq

4
显然,您不能在覆盖修饰符中使用虚拟修饰符。
的确如此。该方法仍然是虚拟的,除非您使用一个也被声明为sealed的方法来覆盖它。(只有当您覆盖某些内容时才能使用sealed修改方法。)来自C# 4规范第10.6.5节:
当实例方法声明包括sealed修饰符时,该方法称为sealed方法。如果实例方法声明包括sealed修饰符,则必须还包括override修饰符。使用sealed修饰符可以防止派生类进一步覆盖该方法。

http://msdn.microsoft.com/en-us/library/88c54tsw.aspx 有一个很好的介绍,当你重写一个方法时使用sealed关键字。 - Mr. Graves

2

这种行为是正确的。如果你不希望继承类覆盖你的虚方法,你可以像这样标记它为“sealed”:

public class Line : DrawingObject
 {
       public sealed override void Draw()
       {
               Console.WriteLine("I'm a Line.");
       }
  }

之后,LittleLine类将无法重写Draw方法。


2
我认为您简单地误解了virtual的工作原理。它不仅限于一级继承,例如在这里解释

对于类中声明或继承的每个虚拟方法,都存在一个相对于该类的最派生实现。关于类R的虚拟方法M的最派生实现如下确定:
•如果R包含M的介绍虚拟声明,则这是M的最派生实现。
•否则,如果R包含M的覆盖,则这是M的最派生实现。
•否则,关于R的M的最派生实现与R的直接基类的M的最派生实现相同。


1

virtual 意味着您的方法通过虚方法表进行分派。如果一个方法是虚拟的,那么对该方法的任何引用都必须通过 vtable 进行。您不能将其非虚拟化,只因为派生类覆盖了它 - 如果 LittleLine 被转换为 DrawingObject,会发生什么?您仍然需要找到正确的实现,这不会是 DrawingObject 的实现。


1

看起来它按预期工作。

dObj [0] = new DrawingObject();
dObj [1] = new Line();
dObj [2] = new LittleLine();

您正在调用Draw()并循环

这是输出的Drawing Object,我是一条线。我是一个小线

dObj [0] .Draw() ->“ Drawing Object” dObj [1] .Draw() ->“我是一条线。” dObj [2] .Draw() ->“我是一个小线”

因此,在Line中的绘制方法似乎被LittleLine覆盖了

是的,这不是您预期的吗?该方法在基类中被标记为虚拟,并被您覆盖。如果要“添加到其中”,则需要使用其他模式。例如装饰器。


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