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

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()。

但在参考中 :

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

在我的逻辑中,CLR首先发现Foo()是一个虚方法,它查看D的方法表,即运行时类型,然后它发现在这个派生类中有一个重写成员,它应该调用它并且永远不会实现inheritance链中有一个new Foo()

我的逻辑出了什么问题?

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

你是从错误的地方开始的。 您的变量是A类型并包含D的实例,因此使用的虚拟表是A1 。 在上面的文字之后,我们检查一个压倒一切的成员。 我们在B找到一个。 C不计算因为它没有覆盖,它正在影响基本方法。 由于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的原因是因为我们正在搜索的inheritance链从A (变量类型)跨越到B (运行时类型)。 CD不再是该链的一部分,也不是虚拟表的一部分。

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

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

我们搜索的inheritance链从CD D有一个重要的成员,所以它被称为Foo

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

我希望这是有道理的。

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

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

虚方法是可以包含方法插槽

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

现在考虑到这一点,让我们看看你的例子。

  • A有一个Foo插槽。
  • B有一个Foo插槽,inheritance自A
  • C有两个Foo插槽。 一个inheritance自B ,一个是新的。 你说你想要一个名为Foo插槽,所以你得到了它。
  • D有两个Foo插槽,inheritance自C

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

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

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

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

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

这就是所谓的。

现在有道理吗?