当实例方法声明包含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 publicvoid 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 virtualvoid 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的实例(通过在其中未隐藏方法的派生程度较小的类型)调用隐藏的虚拟方法。