[<CLR via C#>学习笔记]
[代码皆来自CLR via C#,有改动]
在解释CLR调用这三种方法的区别之前,先看看c#如何生成程序集的:
这幅图只是为了说明IL中间语言代码和元数据的关系,即通过元数据可以找到类型对应的IL代码
接下来看看一个方法被调用时发生的事情:
接下来Main第二次调用Console.WriteLine(string)方法时就不会经过JIT编译器,而是直接执行JIT第一次已经编译好的代码.
CLR之所以可以检测到代码中所有的引用类型,是因为元数据表的引用表记录了源代码中所有的引用类型和成员,而通过这个引用表也就可以获得该方法的IL了,然后JIT编译器也就可以编译这些IL.
现在可以开始对比CLR调用静态方法、实例方法和虚方法的区别了:
定义如下两个类:
class Employee{ public int GetYearsEmployed(){}; public virtual string GetProgressReport(){}; public static Employee Lookup(string name){};}class Manager : Employee{ public override string GetProgressReport(){};}
现在即将调用以下方法M(),
void M(){ Employee e = new Manager(); e = Employee.Lookup("joe"); int year = e.GetYearsEmployed();
e.GetProgressReport();}
假定CLR即将加载到M方法,托管堆已初始化,已创建一个线程栈,当JIT编译器将M方法转换成本机CPU指令时,注意到M()内部的引用类型,创建两个类型对象
执行第一段代码时是new操作符所干的事,结果如下:
1,调用静态方法:
2,调用非虚实例方法时,
3,虚实例方法:
总结:
1,调用静态方法时,CLR会直接定位到定义静态方法的类型对应的类型对象.然后JIT编译器查找方法表中被调用的静态方法对应的记录项.对方法进行JIT编译.
如 e = Employee.Lookup("joe"); 则CLR会定位到Employee类型对象.于是代码就在Employee类型对象的方法表中查找引用了被调用的方法的记录项,对方法进行JIT编译(第一次执行的话),再调用编译好的代码.
2,调用非虚实例方法时,JIT编译器会找到'发出调用的变量的类型'对应的类型对象,查找方法表中被调用的非虚实例方法对应的记录项,对方法进行JIT编译.若找不到该方法则回溯层次结构查找该方法直到Object
如 int year = e.GetYearsEmployed(); 会找到e的类型对应的类型对象.而申明e的代码为: Employee e = new Manager(); 因此此时e被定义为一个Employee对象.于是代码就在Employee类型对象的方法表中查找该方法,如果未找到则回溯层次结构,并沿途查找该方法直到Object.
3,调用虚实例方法时,JIT编译器会生成额外的代码,这些代码用来检查发出调用的变量及其在堆中的地址,并且跟随地址来到堆中实际对象.然后代码检查实际对象内部的'类型对象指针',其指向了该实际对象指向的类型对象.查找方法表中被调用的静态方法对应的记录项.对方法进行JIT编译.
如 e.GetProgressReport(); ,JIT会到堆上的实际对象(根据生成的额外的代码),检查该对象的'类型对象指针',发现它指向了Manager类型对象,于是代码就在Manager类型对象的方法表中查找引用了被调用的方法的记录项,对方法进行JIT编译(第一次执行的话),再调用编译好的代码.
可以发现,调用静态方法直接"跳"到类型对象;类似的调用非虚实例方**找到类型对象(虽然方法不一定在该类型对象中),而相比调用虚实例方法则要"走"两段路才可以找到类型对象.
与此同时,Employee和Manager类型对象也是对象,而他们指向System.Type类型创建的一个特殊的类型对象,而System.Type类型对象也是对象,它内部的“类型对象指针”指向它本身
System.GetType()方法返回的是“类型对象指针”中指向的地址
然而,讨论CLR调用静态方法、实例方法和虚方法的区别实际上好像并没有什么卵用(* ̄︶ ̄)y……
原标题:CLR调用静态方法、实例方法和虚方法的区别
关键词: