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

标签 c# recursion virtual-functions

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

在这种情况下,覆盖方法中对 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 版):

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# Language Specification 中(链接到规范的 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 覆盖中的调用站点有关。虚拟分派(dispatch)确保 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# - 覆盖 C# 的虚方法 - 为什么这不会导致无限递归?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12374981/

相关文章:

php - 在 php 中查询分层的 mysql 表

按字母顺序将 2 个字符串组合到另一个字符串中

c++ - 讨论为什么没有内联虚函数

c# - 如何为 wix 安装程序基本对话框序列指定默认目录

c# - BindingSource 获取当前行

c# - 我可以保证 Response.Filter 的 Stream.Write() 方法不会有部分字符字节吗?

c++ - 虚拟模板方法有意义吗?

c# - LINQ-To-Entity(LINQ to Entities不支持指定类型成员 '')

arrays - 面试题,递归+回溯

c++ - 覆盖限定的虚拟方法