前言
这篇文章出自我个人对C#虚函数特性的研究和理解,未参考、查阅第三方资料,因此很可能存在谬误之处。我在这里只是为了将我的理解呈现给大家,也希望大家在看到我犯了错误后告诉我。
用词约定
“方法的签名”包括返回类型、方法名、参数列表,这三者共同标识了一个方法。
“声明方法”,即指出该方法的签名。“定义方法”,则是指定调用方法时执行的代码。
“同名方法”是指方法的签名相同的两个方法。
“重写”一个方法,意味着子类想继承父类对方法的声明,却想重新定义该方法。
单独使用“使用”一词时,包括“显式”或“隐式”两种使用方式:前者是指在代码中指明,后者是根据语句的上下文推断。
某个类的方法,包括了在该类中定义的方法,以及由继承得到的直接父类的方法。注意这条规则的递归性质。
理论部分
在父类与子类里,除了类之间的继承链,还存在方法之间的继承链。
C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。
virtual 表示允许子类的同名方法与其①建立继承链。
override 表示其①与父类的同名方法之间建立了继承链,并隐式使用 virtual 关键字。
new 表示其切断了其①与父类的同名方法之间的继承链。
sealed 表示将其①与父类的同名方法建立继承链(注意这个就是 override 关键字的特性),并且不允许子类的同名方法与其建立继承链。在使用 sealed 关键字时,必须同时显式使用 override 关键字。
以及:
在定义方法时,若不使用以上关键字,方法就会具有new关键字的特性。对于这一点,如果父类中没有同名方法,则没有任何影响;如果父类中存在一个同名方法,编译器会给出一个警告,询问你是否是想隐藏父类的同名方法,并推荐你显式地为其指定new关键字。
①其:指代正在进行声明的方法。
依照上述的说明,在调用类上的某个方法时,可以为该方法构建出一个或多个“方法继承链”。首先列出从子类②一直到父类③的类继承链,并列出这些类对该方法的最初定义或重定义。然后从父类到子类,逐个检查每个类对该方法的定义,按以下规则构造方法继承链:
任何一个没有使用 override 或 sealed 关键字的方法定义都将成为继承链的开端;
如果该类在定义方法时使用了 virtual 关键字,则会被附加到继承链中。
继承链的结束取决于两个因素:若子类中存在使用了 new 关键字的同名方法,则之前的继承链立刻结束(该方法不会被添加到继承链中);若子类中存在使用了 sealed 关键字的同名方法,则在将该方法添加到继承链后,然后结束继承链。
当你拿到一个子类②的实例,却使用父类③的对象引用调用一个方法时(例如“A instanceRef = new C(); instanceRef.Foo1()”,这时类型A的引用就指向了类型C的对象),C#会先检查该方法是否为一个虚方法(使用了 virtual 关键字):如果不是,则简单地调用该方法的父类③版本即可;如果是,则沿着方法的继承链向下寻找,找到位于继承链底部的那个方法。
②子类:指该实例的实际类型。
③父类:指在调用方法时,使用的对象引用的类型;该类型必然是子类的父类型。