你的位置:首页 > ASP.net教程

[ASP.net教程]CLR调用静态方法、实例方法和虚方法的区别


[<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……