为什么我在覆盖和调用虚拟函数时没有出现堆栈溢出异常

本文关键字:堆栈 异常 栈溢出 函数 覆盖 虚拟 调用 为什么 | 更新日期: 2023-09-27 17:56:00

class Program
{
    static void Main(string[] args)
    {
        B foo = new B();
        foo.DoWork();
        Console.ReadLine();
    }
}
public class A
{
    public virtual void DoWork() { Console.WriteLine("A"); }
}
public class B : A
{
    public override void DoWork() { base.DoWork();  Console.WriteLine("B"); }
}

为什么我没有收到 StackOverflow 异常?据我了解,福。DoWork() 被调用,然后它调用 base。DoWork(),它是虚拟的,在类 B.DoWork() 方法中被覆盖,它会重复调用 base。再次执行 DoWork(),直到堆栈溢出。当使用它代替 base(调用 self 的循环)时,很容易实现这种溢出。在这种情况下,什么可以防止虚拟函数覆盖?

为什么我在覆盖和调用虚拟函数时没有出现堆栈溢出异常

不,当您使用base时,它不会进行虚拟调用。重点是能够调用base实现,即使您已覆盖它

如果您查看生成的 IL,您会发现它不使用 callvirt

IL_0002:  call       instance void A::DoWork()

来自 C# 5 规范的第 7.6.8 节(强调我的):

当基访问引用虚函数成员(方法、属性或索引器)时,在运行时调用哪个函数成员的决定 (§7.5.4) 将更改。调用的函数成员是通过查找函数成员相对于 B 的最派生实现 (§10.6.3) 来确定的(而不是相对于运行时类型,这在非基本访问中很常见)。因此,在虚拟函数成员的重写中,可以使用基访问来调用函数成员的继承实现。如果基访问引用的函数成员是抽象的,则会发生绑定时错误。

A.DoWork是虚拟的。 但是base.命名的方法从来都不是虚拟的。 该语法生成非虚拟调用,因此调用确切的方法,而不是派生最多的版本。

虚拟方法只是可以重写且包含代码的方法。当您调用base.DoWork()时,您明确声明要调用A.DoWork() 。然后,调用A.DoWork()

尝试使A.DoWork()抽象,然后它不能包含代码。然后,您将在base.DoWork()上出现编译错误,因为base.DoWork()中没有任何要执行的内容。