简述
在CLR中,以static修饰符来定义静态方法,而以instance修饰符来定义实例方法,二者的区别是静态方法只与类型相关,在编译期即可确定执行的操作,因此是静态的;而实例方法只与实例相关,在运行期才能决定执行的操作,因此是动态的。
-
方法定义及IL
-
方法调用及IL
- call指令用于执行静态调度,而callvirt用于执行动态调度。
- callvirt使用虚拟调度,也就是根据引用类型的动态类型来调度方法;
- callvirt指令根据引用变量指向的对象类型来调用方法,执行时方法会递归的调用自己直到堆栈溢出,从而实现了在运行时的动态绑定,因此通常用于调用虚方法。
直接调度
直接调度包括:call指令的静态调度和callvirt指令的虚拟调度。
用CIL写程序:从“call vs callvirt”看方法调用
call在某种情况下可以调用虚方法,而callvirt也可以调用非虚方法。具体的情况包括:
(1)call调用虚方法的情况
●密封类型的引用调用虚方法时,采用call调用可以减少callvirt进行类型检查的时间,提高调用性能。
●值类型调用虚方法时,因为值类型首先是密封的,其次call调用可以阻止值类型被执行装箱。
●基类调用虚方法时,采用call可以避免callvirt递归调用本身引起的堆栈溢出。常见的覆写例如,实现System.Object的虚方法Equals()、ToString()时,就采用call调用方式。
(2)callvirt调用非虚方法的情况
●常见于在引用类型中调用非虚方法的情况,其原因是callvirt调用时,如果引用变量为null则会抛出NullReferenceException,而call调用则不会抛出任何异常。为类型安全起见,在C#中会调用callvirt来完成引用类型的非虚方法调用。
call和callvirt指令似乎并无明确的规律,对虚方法与非虚方法的调用没有找到严格的界定,其实这就对了。例如,基于执行性能的考虑,在密封类中以call指令来调用虚方法会更好;而基于安全机制的考虑,则在引用类型中以callvirt指令来调用非虚方法会更安全。
当然,我们对这两个指令的把握,还是有一定的原则可循,这就是:call指令调用静态类型、声明类型的方法,而callvirt调用动态类型、实际类型的方法。万变不离其宗,这条规则就是call指令与callvirt指令骨子里的区别。