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

C# 重写C的虚拟方法-为什么这不会导致无限递归?,c#,recursion,virtual-functions,C#,Recursion,Virtual Functions,我正在查看代码库中的一些代码,但我无法理解它是如何工作的,并且不会因为无限递归而导致堆栈溢出。我在下面粘贴了一些等效代码: 我们在类P1中定义了一个虚拟方法FooB,并在类P2中重写。P2还定义了一个私有的非虚拟方法FooA。B派生自A.P2::FooB最后有一个调用:FooB。我预计这将导致堆栈溢出。 然而,输出是: P2::Foo虚拟 P2::Foo私有非虚拟 在本例中,重写方法中对Foo的第二次调用似乎是拾取非虚拟方法Foo。在P1取消注释代码中执行类似操作时,我们最终通过递归调用Foo无

我正在查看代码库中的一些代码,但我无法理解它是如何工作的,并且不会因为无限递归而导致堆栈溢出。我在下面粘贴了一些等效代码: 我们在类P1中定义了一个虚拟方法FooB,并在类P2中重写。P2还定义了一个私有的非虚拟方法FooA。B派生自A.P2::FooB最后有一个调用:FooB。我预计这将导致堆栈溢出。 然而,输出是: P2::Foo虚拟 P2::Foo私有非虚拟

在本例中,重写方法中对Foo的第二次调用似乎是拾取非虚拟方法Foo。在P1取消注释代码中执行类似操作时,我们最终通过递归调用Foo无数次

问题:最后! 1.为什么原始虚方法和重写方法的行为不同?为什么一个调用自己,另一个调用不同的方法? 2.是否在某个地方指定了优先顺序?请注意,如果我们将private修饰符更改为public,在这两种情况下,我们最终都会调用非虚方法,即使我们以这种方式实例化P2:p1p2=newp2,而不是P2=新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:

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

具体解决您的问题:

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

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

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

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

是否在某个地方指定了优先顺序

是的,在Visual Studio 2012版规范的MSDN页面链接中

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

在这种情况下,可访问性不是一个重要问题。变量p2的类型也不相关,因为您询问的重载解决方案涉及虚方法p2重写中的调用站点。虚拟分派确保Main中的调用调用重写,而不管变量的静态类型如何。在P2的override void FooB中的调用站点上,接收方隐式地是this,它始终具有静态类型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;
}

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

这是C的一个经常被误解的特性:

谢谢phoog!很好的解释!