13

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

在这种情况下,看起来在被覆盖的方法中对 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;
}

}

4

2 回答 2

11

这是因为重载决策仅在无法选择派生类型上定义的重载时才查看继承的成员。从规范(版本 4):

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

具体解决您的问题:

为什么原始虚拟方法和覆盖方法的行为不同?

因为被覆盖的方法是在派生类中定义的,并且该类中存在适用的重载,所以不考虑虚拟方法。不考虑覆盖方法,因为从不考虑覆盖。

为什么一个调用自己而另一个调用不同的方法?

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

是否在某处指定了优先顺序?

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

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

在这种情况下,可访问性不是一个重要问题。变量的类型p2也不相关,因为您询问的重载解决方案涉及P2虚拟方法覆盖中的调用站点。虚拟分派确保Main()调用调用覆盖,而不管变量的静态类型。在P2's中的调用站点override void Foo(B b),接收者是隐式的this,它始终具有静态类型P2

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

不完全的; 如上所述,优先级不是针对非虚拟方法,而是针对在接收器类型中定义的方法(即,调用该方法的对象引用的静态类型)。

于 2012-09-11T17:36:31.737 回答
8

这是 C# 的一个经常被误解的特性:如果派生类中的任何方法适用,则基类中的方法不是候选方法

于 2012-09-11T17:45:25.103 回答