CLR via C#学习笔记-第六章-CLR如何调用虚方法、属性和事件

6.6.1 CLR如何调用虚方法、属性和事件

本节重点是方法,但讨论也与虚属性和虚事件密切相关。属性和事件实际作为方法实现,以后的章节会讨论他们。

方法

方法代表在类型或类型的实例上执行某些操作的代码。在类型上执行操作,称为静态方法;在类型的实例上执行操作,称为非静态方法。

所有方法都有名称、签名和返回类型。CLR允许类型定义多个同名方法,只要每个方法都有一组不同的参数或者一个不同的返回类型。

但除了IL汇编语言,没有任何利用了这一特点的语言。大多数语言包括C#,在判断方法的唯一性时,除了方法名之外,都只以参数为准,方法返回类型会被忽略。

C#在定义转换操作符方法时实际放宽了这个操作,详情在第八章。

以下Employee类定义了三种不同的方法

internal class Employee
{
    //非虚实例方法
    public Int32 GetYearsEmployed(){}
    
    //虚方法
    public virtual String GetProgressReport{}
    
    //静态方法
    public static Employee Lookup(String name){}
}

编译以上代码,编译器会在程序集的方法定义表中写入3个记录项,每个记录项都用一组标值flag指明方法是实例方法、虚方法还是静态方法。

写代码调用这些方法,生成调用代码的编译器会检查方法定义的标值flag,判断应如何生成IL代码来正确调用方法。

CLR提供两个方法来调用指令

  • call

该IL指令可调用静态方法、实例方法和虚方法。

用call指令调用静态方法,必须指定方法的定义类型。用call指令调用实例方法或虚方法,必须指定引用了对象的变量。

call指令假定该变量不为bull。换言之,变量本身的类型指明了方法的定义类型。如果变量的类型没有定义该方法,就检查基类型来查找匹配方法。

call指令经常用于以非虚方式调用虚方法。

  • callvirt

该IL指令可调用实例方法和虚方法,不能调用静态方法。用callvirt指令调用实例方法或虚方法,必须指定引用了对象的变量。

用callvirt指令调用非虚实例方法,变量的类型指定了方法的定义类型。用callvirt指令调用虚实例方法,CLR调查发出调用的对象的实际类型,然后以多态方式调用方法。

为了确定类型,发出调用的变量决不能为null。换言之,编译这个调用时JIT编译器会生出代码来验证变量的值是不是null。

如果是,callvirt指令造成CLR抛出空引用异常。正是由于要进行这种额外的检查,所以callvirt指令的执行速度比call稍慢。

注意,即使callvirt指令调用的是非虚实例方法,也要执行这种null检查。

call和callvirt的实际使用

调用静态方法,IL会调用call指令,调用虚实例方法、非虚实例方法时,IL调用 callvirt方法。

这意味着,当对象为null时,调用对象方法会抛出空引用异常。

但编译器有时用call而不是callvirt调用虚方法,虽然刚开始有点难以理解,但下面代码证明了有时真的需要这样做

internal class SomeClass
{
    //ToString是基类Object定义的虚方法
    public override String ToString()
    {
        //编译器使用IL指令call
        //以非虚方式调用Object的ToString方法
        
        //如果编译器用callvirt而不是
        //那么该方法将递归调用自身,直至栈溢出
        return base.ToString();
    }
}

调用虚方法base.ToString时,C#编译器生成call指令来确保以非虚方式调用基类的ToString方法。

这是必要的,因为如果以虚方式调用ToString,调用会递归执行。

值类型倾向使用call

编译器调用值类型定义的方法时倾向于使用call指令,因为值类型是密封的。

这意味着即使值类型含有虚方法也不要考虑多态性,这使调用更快。

此外,值类型实例的本质保证他永不为null,所以永不抛出空引用异常。

最后,如果以虚方式调用值类型中的虚方法,CLR要获取对值类型的类型对象的引用,以便引用(类型对象中的)方法表,这要求对值类型装箱。

装箱对堆造成更大压力,迫使更频繁的垃圾回收,使性能受到影响。

无论用call还是callvirt调用实例方法还是虚方法,这些方法通常接收隐藏的this实参作为方法的第一个参数。this实参引用要操作的对象。

类型的设计原则

类型设计的时候应尽量减少虚方法数量。首先调用虚方法的速度比调用非虚方法慢。其次,JIT编译器不能内嵌inline虚方法,这进一步影响性能。第三,虚方法使组建版本控制变得更脆弱。第四,定义基类型时,经常要提供一组重载的简便方法convenience method。如果希望这些方法是多态的,最好的办法就是使最复杂的方法成为虚方法,使所有重载的简便方法成为非虚方法。

遵循这个原则,还可在改善组件版本控制的同时,不至于对派生类型产生负面影响。 

猜你喜欢

转载自www.cnblogs.com/errornull/p/9757092.html
今日推荐