当实例方法声明包含 virtual 修饰符时,称该方法为虚拟方法。不存在 virtual 修饰符时,称该方法为非虚拟方法。非虚拟方法的实现是不变的:无论是在声明它的类的实例上调用该方法还是在派生类的实例上调用,实现都是相同的。与此相反,虚拟方法的实现可以由派生类取代。取代所继承的虚拟方法之实现的过程称为重写方法在虚拟方法调用中,为其进行调用的实例的运行时类型确定要调用的实际方法实现。在非虚拟方法调用中,实例的编译时类型是决定性因素。准确地说,当在具有编译时类型 C 和运行时类型 R 的实例(其中 R 为 C 或者从 C 派生的类)上用参数列表 A 调用名为 N 的方法时,调用按下面这样处理: 首先,将重载决策应用于 C、N 和 A,以从在 C 中声明和由 C 继承的方法集中选择一个特定方法 M。 
然后,如果 M 为非虚拟方法,则调用 M。 
否则,M 为虚拟方法,调用就 R 而言 M 的派生程度最大的实现。 
对于在类中声明或者由类继承的每个虚拟方法,存在一个就该类而言的派生程度最大的实现。就类 R 而言虚拟方法 M 的派生度最大的实现按下面这样确定: 如果 R 包含 M 的引入 virtual 声明,则这是 M 的派生程度最大的实现。 
否则,如果 R 包含 M 的 override,则这是 M 的派生程度最大的实现。 
否则,M 的派生程度最大的实现与 R 的直接基类的派生程度最大的实现相同。 
下列实例阐释虚拟方法和非虚拟方法之间的区别:class A
{
   public void F() { Console.WriteLine("A.F"); }
   public virtual void G() { Console.WriteLine("A.G"); }
}
class B: A
{
   new public void F() { Console.WriteLine("B.F"); }
   public override void G() { Console.WriteLine("B.G"); }
}
class Test
{
   static void Main() {
      B b = new B();
      A a = b;
      a.F();
      b.F();
      a.G();
      b.G();
   }
}
在该示例中,A 引入一个非虚拟方法 F 和一个虚拟方法 G。类 B 引入一个新的非虚拟方法 F,从而隐藏了继承的 F,并且还重写了继承的方法 G。此例产生下列输出:A.F
B.F
B.G
B.G
请注意,语句 a.G() 调用 B.G 而不是 A.G。这是因为是实例的运行时类型(即 B)而不是实例的编译时类型(即 A)确定要调用的实际方法实现。由于允许方法隐藏继承的方法,因此类可以包含具有相同签名的若干个虚拟方法。由于除派生程度最大的方法外全部都被隐藏,因此这不会造成多义性问题。在下面的示例中,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();
   }
}
C 类和 D 类包含两个具有相同签名的虚拟方法:A 引入的虚拟方法和 C 引入的虚拟方法。C 引入的方法隐藏从 A 继承的方法。因此,D 中的重写声明重写 C 引入的方法,而 D 不可能重写 A 引入的方法。此例产生下列输出:B.F
B.F
D.F
D.F
请注意,可以通过访问 D 的实例(通过在其中未隐藏方法的派生程度较小的类型)调用隐藏的虚拟方法。