以下都过度简化并将一种可能的实现视为事实,但应该足以拥有一个有效的心理模型。
当调用代码“知道”一个类时,它知道以下内容:
可以在距对象位置的特定偏移处访问字段。例如,如果一个对象位于地址 120,并且有两个整数字段,那么它可能能够在地址 124 访问它们。如果另一个相同类型的对象位于地址 140,则等效字段将位于 144。
非虚拟方法(和属性可以被认为是一个或两个方法的语法糖)是位于特定地址的函数,它引用您正在调用的对象(this
从方法内部)和该函数的其他参数。
虚拟方法与上面类似,但它们的地址可以通过查看与类关联的表中的特定偏移量来找到,该表的地址也将是类地址的特定偏移量。
在此,Kid
有一个方法表,它是Parent
(它可以添加更多方法)的超集,并且对于那些它没有覆盖的方法具有相同的函数地址(调用Equals
它使用与调用相同的函数Equals
在 a 上Parent
),但它覆盖的地址不同(Print()
在这种情况下)。
因此,如果你有一个Kid
then 无论你是通过Parent
引用还是Kid
引用,调用Print()
都会查看同一个表,查找Print()
方法的位置,然后调用它。
在 的情况下Child
,new
使用Print
方法。这告诉编译器我们特别想要一个不同的表。因此,如果我们Print()
通过Child
引用调用,它会查找Child
特定表,并调用它找到的方法。Kid
但是,如果我们通过 a or引用调用它Parent
,那么我们甚至不知道Child
我们可以使用一个特定的表,我们分别在我们知道Kid
和Parent
拥有的表中查找函数,并调用找到的函数(即中定义Kid
)。
作为一项规则,new
是要避免的。它的用途有两个地方:
一是向后兼容。例如,如果Child
有一个Name
属性,然后后来Parent
更改了 for 的代码,使其也有一个Name
属性,我们就会发生冲突。由于Child
'sName
不是一个覆盖,它被视为好像它有new
但给我们一个警告,因为这是使用旧事物方式的代码和知道新事物的代码可以共存的唯一Name
方式Parent
。如果我们回来重新编译Child
,我们可能应该重构使其没有自己的Name
(如果 onParent
做我们想要的),重构使其成为覆盖,重构为完全不同的东西,或添加new
以表明这就是我们想要的样子,尽管它并不理想。
另一种是new
允许基类方法允许的相同行为的更具体形式,但在逻辑上兼容(因此用户不会感到惊讶)。后者应该进入半高级技术框,不要轻易完成。也应该这样评论,因为大多数时候看到new
意味着你处理的东西充其量是一种妥协,可能应该改进。
Kid
(旁白:我是唯一一个看到s有Child
仁就想到小报的人吗?)