从来就不可能精通:关于Boxing

我的简历上从来就不敢出现“精通”两个字,但是每次有招聘简历推荐过来的时候,各种“精通”就映入了我的眼帘。无奈啊,确实还是不行啊。例如今天在逛园子的时候发现这篇文章的回复中出现了关于boxing的讨论。发现有一个情况我原来的理解是错误的。于是留此一篇备忘。

一般来说当一个 value type 实例需要转换为一个 reference type 实例的时候需要进行装箱。例如:

int number = 2;
object o = number;
o.ToString();

如果查看其IL,可以发现 box 指令。在一般情况下,例如将value type实例 cast为一个reference type(或者接口形式)实例,或者函数参数的类型转换(转换为 reference type,或者cast 为 接口)。但是只有 box 指令才说明发生了装箱的操作吗?实际上还有其他的情况。

首先,value type和其他的类型一样,都可以拥有自己的静态的或者实例的成员,包括方法(包括虚方法)和field。我们当然可以调用其虚方法,但是当我们必须通过虚函数表去查找虚函数的时候则必须boxing然后再去callvirt,如果仅仅是作为成员函数调用value type实例的虚函数实现的话则不需要boxing,直接用call指令就OK了。例如:

int number = 3;
number.ToString();

其 IL 代码是:

IL_0001:  ldc.i4.3    
IL_0002:  stloc.0     
IL_0003:  ldloca.s    00 
IL_0005:  call        System.Int32.ToString

这里直接调用 call 方法触发实例方法。并不用装箱。

但是如果这样,令value type实例调用其没有实现的虚方法:

var valueInstance = new ValueClass();
valueInstance.ToString();

就会产生如下的 IL 代码:

IL_0001:  ldloca.s    00 
IL_0003:  initobj     ValueClass
IL_0009:  ldloca.s    00 
IL_000B:  constrained. ValueClass
IL_0011:  callvirt    System.Object.ToString

其中没有 box 指令, 但是有 constrained. 指令。constrained. 指令和 callvirt 使用称为 constrained virtual call,这是在 CLR 2.0引入的。主要目的是为了处理泛型类型的实例化或者方法调用,不管泛型类型实际参数是value type 还是 reference type。但是constrained. 指令后面跟的类型参数并不一定非得是泛型参数,可以直接是具体类型。这种调用的规则如下:

  • 如果是一个reference type(此时这个实例的 this 指针是一个managed pointer,指向该reference type 实例),则this指针复引用返回的是reference type实例的引用。虚函数的调用就发生在reference type实例上。
  • 如果是一个value type(此时这个实例的this指针是一个managed pointer,指向该 value type实例),并且该value type实现了该函数,那么接下来的调用实际上是一个非虚调用,并直接作用在该 value type 实例上(这是因为 value type 都是 seal 的,其实现了的虚方法不可能再被其他的派生类使用)。
  • 如果是一个value type,并且该类型并没有实现基类的虚方法(其基类肯定是System.Object,System.ValueType或者System.Enum),则this指针的复引用会返回value type实例,并直接被boxing为object reference。virtcall会作用在object reference上。

我们可以看到,最后一条就是我们前一段范例代码中的情况。

当然,调用基类的非虚方法也需要boxing,例如:

var valueInstance = new ValueClass();
valueInstance.GetType();

其首先会装箱, 然后直接用 call 调用基类的非虚方法。

IL_0001:  ldloca.s    00 
IL_0003:  initobj     ValueClass
IL_0009:  ldloc.0     
IL_000A:  box         ValueClass
IL_000F:  call        System.Object.GetType

总结一下,在以下的情况下可能发生装箱:

  1. 值类型和引用类型(接口)的类型转换,参数传递;
  2. 调用值类型实例未实现的基类的虚方法;
  3. 调用值类型父类的非虚方法。

还有其它情况吗?欢迎补充。

posted @ 2012-11-29 23:12  TW-刘夏  阅读(1903)  评论(6编辑  收藏  举报