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
的实例,因此使用的虚拟表是A
的1 。 在上面的文字之后,我们检查一个压倒一切的成员。 我们在B
找到一个。 C
不计算因为它没有覆盖,它正在影响基本方法。 由于D
会覆盖C
,而不是A
或B
,因此它也不会计算。 我们正在寻找最派生类中的重要成员。
所以找到的方法是B.Foo()
。
如果更改C
使其覆盖而不是阴影,则找到的方法将为D
,因为它是派生最多的重写成员。
如果您改为将对象更改为B
或C
的实例,则B.Foo()
仍将是所选的覆盖。 澄清一下,这就是我的意思:
A tan = new B(); tan.Foo(); // which foo is called? Why, the best Foo of course! B!
调用B
的原因是因为我们正在搜索的inheritance链从A
(变量类型)跨越到B
(运行时类型)。 C
和D
不再是该链的一部分,也不是虚拟表的一部分。
如果我们改为将代码更改为:
C tan = new D(); tan.Foo(); // which foo, which foo?
我们搜索的inheritance链从C
到D
D
有一个重要的成员,所以它被称为Foo
。
假设您添加了另一个inheritance自A
类Q
,以及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
那是插槽。 那么,那些插槽里有什么?
- 在
A
,A.Foo
进入插槽。 - 在
B
,B.Foo
进入插槽。 - 在
C
,B.Foo
进入第一个槽,C.Foo
进入第二个槽。 请记住,这些插槽完全不同 。 你说你想要两个同名的插槽,这就是你得到的。 如果那令人困惑,那就是你的问题。 如果你这样做会伤害它,不要这样做。 - 在
D
,B.Foo
进入第一个槽,D.Foo
进入第二个槽。
那么现在你的电话会怎么样?
编译器的原因是您在编译时类型A
上调用Foo
,因此它在A
找到第一个(也是唯一的) Foo
插槽。
在运行时,该槽的内容是B.Foo
。
这就是所谓的。
现在有道理吗?