覆盖C#的虚方法 - 为什么这不会导致无限递归?

时间:2012-09-11 17:32:33

标签: c# recursion virtual-method

在我们的代码库中查看一些代码并且我无法理解这是如何/为什么它甚至工作(并且由于无限递归而不会导致堆栈溢出)。我在下面粘贴了一些等效的代码: 我们有一个虚拟方法Foo(B)在P1类中定义并在P2类中重写。 P2还定义了私有非虚方法Foo(A)。 B派生自A. P2 :: Foo(B)最后有一个调用:Foo(b)。我希望这最终会导致堆栈溢出。 但是,输出是: P2 :: Foo Virtual P2 :: Foo私有非虚拟

看起来在重写方法中对Foo的第二次调用就是在这种情况下拾取非虚方法Foo。在P1(取消注释代码)中执行类似操作时,我们最终通过递归调用Foo无限次。

问题:(终于!) 1.为什么原始虚方法和重写方法的行为不同?为什么一个人在调用自己而另一个人调用另一种方法? 2.某处是否有指定的优惠顺序?请注意,如果我们将private修饰符更改为public,在这两种情况下,我们最终都会调用非虚方法(即使我们以这种方式实例化P2:P1 p2 = new P2();而不是P2 p2 = new P2( );)看起来非虚拟版本是首选,除非它在虚拟方法定义中。这是真的吗?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
public class P1
{
    static void Main(string[] args)
    {
        B b = new B();
        P2 p2 = new P2();
        p2.Foo(b);
        // Uncomment code to cause infinite recursion
        //P1 p1 = new P1();
        //p1.Foo(b);
    }

    private void Foo(A a)
    {
        Console.WriteLine("P1::Foo Private Non-Virtual");
    }

    public virtual void Foo(B b)
    {
        Console.WriteLine("Inside P1::Foo");
        // Uncomment code to cause infinite recursion
        // Foo(b);
    }
}

public class P2 : P1
{
    private void Foo(A a)
    {
        Console.WriteLine("P2::Foo Private Non-Virtual");
    }

    public override void Foo(B b)
    {
        Console.WriteLine("P2::Foo Virtual");
        Foo(b);
    }
}

public class A
{
    public int a = 10;
}

public class B : A
{
    public int b = 20;
}

}

2 个答案:

答案 0 :(得分:11)

这是因为重载解析只查看继承的成员,如果它不能选择在派生类型上定义的重载。从规范(版本4):

  

例如,方法调用的候选集不包括标记为override的方法(第7.4节),如果派生类中的任何方法适用(第7.6.5.1节),则基类中的方法不是候选方法。

具体解决您的问题:

  

为什么原始虚方法和重写方法的行为不同?

由于重写方法是在派生类中定义的,并且该类中存在适用的重载,因此不考虑虚方法。不考虑重写方法,因为从不考虑覆盖。

  

为什么一个人在调用自己而另一个人调用另一种方法?

上面解释了派生类中的行为。在基类中,重载解析的最佳候选者是虚方法本身,因为它更具体(B来自A)。

  

是否在某处指定了偏好顺序?

是,在C# Language Specification(指向Visual Studio 2012版本规范的MSDN页面的链接)。

  

注意,如果我们将private修饰符更改为public,在这两种情况下,我们最终都会调用非虚方法(即使我们以这种方式实例化P2:P1 p2 = new P2();,而不是P2 p2 =新的P2();)

在这种情况下,辅助功能不是一个重要问题。变量p2的类型也不相关,因为您询问的重载决策涉及P2覆盖虚拟方法中的调用站点。无论变量的静态类型如何,虚拟分派都可确保Main()中的调用调用覆盖。在P2的{​​{1}}中的呼叫网站,接收方隐式override void Foo(B b),其静态类型为this

  

看起来非虚拟版本是首选,除非它在虚拟方法定义中。这是真的吗?

不完全;如上所述,首选项不适用于非虚方法,而是适用于接收器类型中定义的方法(即,调用该方法的对象引用的静态类型)。

答案 1 :(得分:8)