从来就不可能精通:关于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
总结一下,在以下的情况下可能发生装箱:
- 值类型和引用类型(接口)的类型转换,参数传递;
- 调用值类型实例未实现的基类的虚方法;
- 调用值类型父类的非虚方法。
还有其它情况吗?欢迎补充。