CLR via C#-CLR调用方法

CLR调用方法

方法代表在类型或类型的实例上执行某些操作的代码。

在类型上执行操作,称为静态方法;在类型的实例上执行操作,称为非静态方法。所有方法都有名称、签名和返回类型。

在判断方法的唯一性时,除了方法名之外都只以参数为准,方法返回类型会被忽略。

不过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指令假定该变量不为null,变量本身的类型指明了方法的定义类型。

如果变量的类型没有定义该方法,就检查基类型来查找匹配方法。

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虚方法,这进一步影响性能。

③虚方法使组建版本控制变得更脆弱。

④定义基类型时,经常要提供一组重载的简便方法。

如果希望这些方法是多态的,最好的办法就是使最复杂的方法成为虚方法,使所有重载的简便方法成为非虚方法。


隐藏基类的同名实例方法

假设Phone类型定义了Dial方法

public class Phone{
    public void Dial() => Console.WriteLine("Phone.Dial");  
}

假设BetterPhonej以Phone类型作为基类型

public class BetterPhone:Phone{
    public void Dial() => Console.WriteLine("BetterPhone.Dial");
}

编译上述代码时,C#编译器会警告BetterPhone类正在定义这个Dial方法,他会隐藏Phone类定义的Dial。

解决办法就是在BetterPhone类中定义Dial时,在前面加一个new关键字。

public new void Dial() => Console.WriteLine("BetterPhone.Dial");
//测试类
BetterPhone bp = new BetterPhone();
bp.Dial();
//结果
BetterPhone.Dial

隐藏基类同名的虚方法

假定Phone类型添加虚方法Message,BetterPhone重写了该方法。并且BetterPhone的Dial方法调用了Message方法。

public class Phone{
    public void Dial(){
        Console.WriteLine("Phone.Dial");
        Message();
    }
    public virtual void Message() => Console.WriteLine("Phone.Message");
}
public class BetterPhone : Phone{
    public new void Dial(){
        Console.WriteLine("BetterPhone.Dial");
        base.Dial();
    }
    public virtual void Message() => Console.WriteLine("BetterPhone.Message");

}

BetterPhone类型会生成警告,要给BetterPhone的Message方法删除virtual关键字添加override关键字,或者直接添加new关键字

添加new关键字

public new virtual void Message() => aConsole.WriteLine("BetterPhone.Message");
//测试结果
BetterPhone.Dial
Phone.Dial
Phone.Message

在这段代码中,关键字new告诉编译器生成元数据,让CLR知道BetterPhone类型的Message方法应被视为由BetterPhone类型引入的新函数。

删除virtual关键字添加override关键字

public overridel void Message() => aConsole.WriteLine("BetterPhone.Message");
//测试结果
BetterPhone.Dial
Phone.Dial
BetterPhone.Message
原文地址:https://www.cnblogs.com/errornull/p/9757092.html