现在的位置: 首页 > 综合 > 正文

.NET中的方法及其调用

2013年02月18日 ⁄ 综合 ⁄ 共 3544字 ⁄ 字号 评论关闭
前言:《Essential.NET》这本书是.NET中的经典之作,个人感觉和《Thinking in C++》有的一拼,在我床头除了《设计模式》就只有它了:)。正在学习其方法一节,发现方法(Method)才是本书的重点,全书花了不少纸在写这一块。在下记录一些个人的学习心得,一方面和大家分享,另一方面也请大家指点本人在理解上的错误和不足。
按书上介绍的顺序来。

首先讲到JIT的问题。值得引用的一段:“CLR只执行本机代码。如果一个方法体由CIL组成,那么它就必须在调用之前被转换为本机代码。”这儿转换方法有两种,一是当组件被首次部署在目标机器上时,通过预编译生成本机映像;另一个就是JIT了。在此作者推荐的方法是JIT,理由:
1、本机映像的代码尺寸过大(有多大?不太清楚),如果业务逻辑只使用组件的一到两个方法,岂不浪费。
2、跨组件的约定,个人理解是如果有代码涉及到COM之类的组件,本机映像对其的“链接”是静态的,非本机映像的组件如果有所变更那本机映像就会出问题。
 
其次,关于方法表。记的以前看C++的书籍时也听过C++中方法表的介绍,.NET中也有方法表。在.NET中每个类型都会有一个CORINFO_CLASS_STRUCT的数据结构,有Equals、GetHashCode之类的方法,也有用户自定义的方法在里边。这个结构为方法提供了入口点。而默认的,每个入口点都指向了一个stub routine(书上是叫“存根例程”),即是对JIT编译器进行调用。所以在JIT进行编译之前我们可以知道每一个方法调用的不是某段代码,而是JIT,具体代码的位置要等JIT来给定。在JIT之前我们看到stub routine中的数据应该都是call xxxxxxxx这样的东东。等JIT生成本机代码后,stub routine中的内容会变为jmp XXXXXXXX,跳转到要执行的代码。我想这样带来的好处是,如果某方法不再被使用(unreachable in GC),那JIT可以忽略它以减少系统开销;或是对方法表进行优化,让常用的方法被放在同一个内存页面中。 
第三,虚方法
以上我们看到的都是一般的方法,虚方法与其有所区别。我们知道如果一个方法是虚方法,那么它有可能会被子类重写,所以如果按上边我们介绍的jmp到某地址在父类中是不行的。(写到这儿肚子饿了,标记一下。)其实CLR是把虚方法和一般方法分开来放的,两种方法的入口项都不一样,方便子类对虚方法进行索引。虚方法的入口操作码不是上边我们看到的call,而是callvirt。虚方法具体的方法表是依靠目标对象的RuntimeTypeHandle来决定。参考下列代码:
      Mov ecx, esi
      Mov eax, dword ptr [ecx]
      Call dword ptr [eax + 38h]
对此书上有代码的详细介绍。其中第二个mov是对第三句的call进行准备的,call的时候是利用句柄+offset来进行虚方法的寻址,offset在类型的生命周期中是静态的,这也是虚方法的一大特色。书上同时提到了一些元数据特性,比较强调的是newslot,代码中与其对应的就是用new关键字声明的方法。从语法上来看是用来禁用(或是说屏蔽)父类的方法,消除编译警告,而对底层来说它的作用是重新分配虚方法表的索引。想想其性能如何,各位不妨写代码看看?
虚方法还有其他用处,比如声明成sealed之类的。
 
第四、接口
.NET有自己的接口偏移表。CLR在初始化每个接口类型时(每一个接口类型,那范围多大?Modules?Assembly?AppDomain?这是本人的一个疑问。)都会在这个偏移表中有所对应。而对于具体某个类来说,又有自己的一个接口偏移表。同时要注意的是接口表中的方法在CORINFO_CLASS_STRUCT中的被连续地存放的。对于基于接口的方法的调用,系统是先找到所属接口,在找到后再确定方法的offset,然后进行调用,也有提到对性能有所影响。不过也有说到有相同的对象引用如果使用频繁,则优化就是可能的,个人猜想是将常用的接口尽量安排在一个连续的区域。
对于接口的使用:
一般而言我们认为接口中的方法都可以是public的,因为接口暴露出方法如void f()。然后我们的某个类可以实现这个接口,比如class a: ISomeThing这样,再在类代码中写类似public void f()这样的代码,这样我们在new这个方法后就可以使用这个f()了。但.NET还有另一种方式,不知道是不是为面向接口编程专门留出来的,即在类代码中声明f()的形式是void ISomeThing.f(){……}这样。这样的代码通过类是不能访问到是,也就是说f()对于该类而言是private,但对接口ISomeThing却是public的,这样我们可以通过接口访问而不是类。书中也提出一种可能的情况,即一个类可以实现N个接口,但多个接口之间可能出现方法名冲突,这时不直接使用类来执行接口方法而是用接口来执行,会方便很多。除了Interface.MethodName的东东以外,还有一些其它内容,本人根据书中代码用Snippet Compiler写出一小段代码以示区别:
using System;
using System.Collections;

public class MyClass
{
    
public static void Main()
    
{
        ReallyDerived r1 
= new ReallyDerived();
        Derived        r2 
= r1;
        Base        r3 
= r1;
        ICommon    r4 
= r1;
        
        r1.DoIt();
        r2.DoIt();
        r3.DoIt();
        r4.DoIt();

        RL();
    }

    
    
#region Helper methods
    
private static void WL(object text, params object[] args)
    
{
        Console.WriteLine (text.ToString(), args);    
    }

    
private static void RL()
    
{
        Console.ReadLine ();    
    }

    
private static void Break() 
    
{
        System.Diagnostics.Debugger.Break();
    }

    
#endregion

}

public interface ICommon {
  
void DoIt();
}


public class Base : ICommon {
  
void ICommon.DoIt() { System.Console.WriteLine("Base : void ICommon.DoIt()"); }
  
public virtual void DoIt() { System.Console.WriteLine("Base : public virtual void DoIt()"); }
}


public class Derived : Base, ICommon {
  
void ICommon.DoIt() { System.Console.WriteLine("Derived: void ICommon.DoIt()");}
  
public new virtual void DoIt() { System.Console.WriteLine("Derived: public new virtual void DoIt()"); }
}


public class ReallyDerived : Derived {
  
public override void DoIt() { System.Console.WriteLine("ReallyDerived: public override void DoIt()");}
}

运行出来情况如图: 
对此本人有一个疑问:为什么结果的第二行和第一行一样?请高手解答。 
第五、显式方法调用
书中没有对显式方法的调用作过于精细的底层分析,主要讲了MethodInfo中Invoke方法的两个原型。大部分的内容我们可以参考书的第一节Reflector。

抱歉!评论已关闭.