关于虚方法覆盖C#:虚方法覆盖C# – 为什么这不会导致无限递归?

Virtual method overriding C# - why doesn't this cause an infinite recursion?

在我们的代码库中查看了一些代码,我无法理解这是如何/为什么工作的(并且不会由于无限递归而导致stackoverflow)。我在下面粘贴了一些等效代码:我们有一个虚拟方法foo(b),在类p1中定义,在类p2中重写。p2还定义了私有非虚拟方法foo(a)。b从a派生。p2::foo(b)在末尾有一个调用:foo(b)。我希望这最终会导致堆栈溢出。但是,输出是:P2::FO虚拟p2::foo私有非虚拟

在这种情况下,对重写方法中foo的第二个调用似乎是获取非虚拟方法foo。在p1(取消注释代码)中执行类似操作时,我们通过递归无限次调用foo。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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)中:

For example, the set of candidates for a method invocation does not include methods marked override (§7.4), and methods in a base class are not candidates if any method in a derived class is applicable (§7.6.5.1).

具体解决您的问题:

Why is the behavior different in the original virtual method and the overridden method?

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

Why is one calling itself and the other calling a different method?

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

Is there an order of preference specified somewhere?

是的,在C语言规范中(链接到该规范的Visual Studio 2012版本的msdn页)。

Note that if we change the private modifier to public, in both cases, we end up calling the non-virtual method (Even if we instantiate P2 this way: P1 p2 = new P2(); , instead of P2 p2 = new P2();)

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

It looks like the non-virtual version is preferred, except when it is inside a virtual method definition. Is this true?

不完全正确;正如上面所解释的,首选项不是非虚拟方法,而是接收器类型中定义的方法(即,调用方法的对象引用的静态类型)。


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