装箱与拆箱
上一节我们讲了什么是值类型和引用类型,这节我们讨论一下什么是装箱和拆箱操作。
装箱:是将值类型转换成引用类型的过程。
拆箱:是将引用类型转换成值类型的过程。
这两句话其实就已经把装箱和拆箱讲完了,但是如果我们深入理解的话,就会发现并没有这么简单。
首先我们看一个最简单的装箱代码:
1 object obj = 26;
我们都应该知道,26是值类型,obj是一个引用类型的变量,现在我们把一个值类型的变量赋值给一个引用类型的变量,在这个过程中就发生了装箱的操作。
我们通过ILDasm查看IL代码来了解一下,在装箱过程中所执行的操作:
.locals init ([0] object obj) //声明类型为object的变量obj,索引值为0 IL_0000: nop IL_0001: ldc.i4.s 26 //将整型26压入栈顶 IL_0003: box [mscorlib]System.Int32 //执行装箱操作。具体步骤是:先在托管堆中申请存放一个System.Int32类型变量所需要的内存空间;然后将值类型的值复制到新分配的内存空间中;最后返回新分配的内存空间的地址 IL_0008: stloc.0 //弹出堆栈上的值,并将它存储到索引值为0的局部变量中,也就是把26在内存中的地址保存到变量obj中(这个地方的26是复制的26,而不是原来的26,此时obj中存放的就已经是26的内存地址,也就是说当前obj已经指向托管堆中的实例对象) IL_0009: ret
我们通过下面的代码来了解一下拆箱的过程:
1 object obj = 20; 2 int res = (int)obj;
上边的代码先进行了一次装箱操作,然后又进行了一次拆箱操作,同样,我们通过查看IL代码来了解整个拆箱的过程:
.locals init ([0] object obj, [1] int32 num) //声明两个局部变量,object类型的obj变量和int类型的num IL_0000: nop IL_0001: ldc.i4.s 20 //将20压入栈顶 IL_0003: box [mscorlib]System.Int32 //执行装箱操作 IL_0008: stloc.0 //将栈中的值弹出,并将值赋值给索引为0的局部变量,也就是把20在内存中的地址赋值给变量obj IL_0009: ldloc.0 //将索引为0的局部变量的地址压入栈顶,也就是把变量obj的地址压入栈顶 IL_000a: unbox.any [mscorlib]System.Int32 //执行拆箱操作,将object类型装换成System.Int32类型 IL_000f: stloc.1 //将堆栈中的值弹出,并将值赋值给索引为1的局部变量,也就是把20赋值给变量num IL_0010: ret
装箱操作和拆箱操作是要额外耗费cpu和内存资源的,所以在c# 2.0之后引入了泛型来减少装箱操作和拆箱操作。
以下是对IL代码中用到的指令的解释:
名称 |
作用 |
ldc.i4.s |
将提供的 int8 值作为 int32 推送到计算堆栈上(短格式) |
box |
将值类转换为对象引用(object类型) |
stloc.s |
从计算堆栈的顶部弹出当前值并将其存储在局部变量列表中的索引值为 index 处(短格式) |
ldloca.s |
将特定索引处的局部变量的地址加载到计算堆栈上(短格式) |
unbox |
将值类型的已装箱的表示形式转换为其未装箱的形式 |