CLR会检查整个继承链以确定要调用哪个虚方法吗?

时间:2017-08-25 00:42:44

标签: c# .net inheritance

继承链如下:

class A
    {
        public virtual void Foo()
        {
            Console.WriteLine("A's method");
        }
    }

class B:A
    {
        public override void Foo()
        {
            Console.WriteLine("B's method");
        }
    }

class C:B
    {
        public new virtual void Foo()
        {
            Console.WriteLine("C's method");
        }
    }

class D:C
    {
        public override void Foo()
        {
            Console.WriteLine("D's method");
        }
    }

然后:

class Program
    {
        static void Main(string[] args)
        {
            A tan = new D();
            tan.Foo();
            Console.Read();
        }
    }

结果是,调用了B类中的方法foo()。

但在reference

  

调用虚方法时,对象的运行时类型为   检查一个压倒一切的成员。最重要的成员   调用派生类,如果不是,则可以是原始成员   派生类已重写成员。

在我的逻辑中,CLR首先发现Foo()是一个虚方法,它查看D的方法表,运行时类型,然后它发现在这个派生程序中有一个重写成员class,它应该调用它,永远不会意识到继承链中有new Foo()

我的逻辑出了什么问题?

2 个答案:

答案 0 :(得分:34)

艾米的回答是正确的。以下是我喜欢看这个问题的方法。

虚拟方法是一个插槽,可以包含方法

当要求进行重载解析时,编译器确定在编译时使用插槽 。但运行时确定该插槽中实际的方法

现在请记住你的例子。

  • AFoo个插槽。
  • B有一个Foo的插槽,已继承 来自A
  • C有两个Foo位。一个继承自B,和 一个新的。你说你想要一个名为Foo 的 new slot ,所以你得到了它。
  • D有两个Foo位置,继承自C

那是插槽。那么,那些插槽里有什么?

  • A中,A.Foo进入广告位。
  • B中,B.Foo进入广告位。
  • C中,B.Foo进入第一个广告位,C.Foo进入第二个广告位。请记住,这些插槽完全不同。你说你想要两个同名的插槽,这就是你得到的。如果那令人困惑,那就是你的问题。如果你这样做会伤害,不要这样做。
  • D中,B.Foo进入第一个广告位,D.Foo进入第二个广告位。

那么现在你的电话会怎么样?

编译器的原因是您在编译时类型Foo上调用A,因此它会找到Foo上的第一个(也是唯一的)A个插槽。

在运行时,该插槽的内容为B.Foo

这就是所谓的。

现在有意义吗?

答案 1 :(得分:29)

  

调用虚方法时,将检查对象的运行时类型是否有覆盖成员。如果没有派生类重写成员,则调用派生程度最大的类中的重写成员,该成员可能是原始成员。

你是从错误的地方开始的。您的变量的类型为A,并且包含D的实例,因此使用的虚拟表是A 1 。在上面的文字之后,我们检查一个压倒一切的成员。我们在B中找到了一个。 C不计算,因为它没有覆盖,它是 shadowing 基本方法。由于D会覆盖C,而不是AB,因此它也不会重要。我们正在寻找最派生类中的重写成员。

所以找到的方法是B.Foo()

如果更改C使其覆盖而不是阴影,则找到的方法将为D,因为它是派生最多的覆盖成员。

如果您将对象更改为BC的实例,B.Foo()仍将是所选的覆盖。澄清一下,这就是我的意思:

A tan = new B();
tan.Foo();    // which foo is called?  Why, the best Foo of course!  B!

调用B的原因是因为我们正在搜索的继承链从A(变量类型)跨越到B(运行时类型)。 CD不再是该链的一部分,也不属于虚拟表。

如果我们改为将代码更改为:

C tan = new D();
tan.Foo();  // which foo, which foo?

我们搜索的继承链从C跨越到DD有一个重要成员,因此调用Foo

假设您添加了继承自Q的{​​{1}}和A继承的另一个类R,依此类推。你有两个继承分支,对吧?在搜索大多数派生类型时选择哪个?遵循从Q(您的变量类型)到A(运行时类型)的路径。

我希望这是有道理的。

1 不是字面意思。虚拟表属于D,因为它是运行时类型并包含其继承链中的所有内容,但它有用且更容易将D视为起点。毕竟,您正在搜索派生类型。