值类型是比引用类型更“轻型”的一种类型,因为它们不作为对象在托管堆中分配,不会被垃圾回收,也不通过指针来引用。但在许多情况下,都需要获取对值类型的一个实力的引用。例如假定要创建一个ArrayList对象(System.Collections命名空间中定义的一个类型)来容纳一组Point结构,那么代码可能像下面这样:
struct Point{
public Int32 x,y;
}
public sealed class Program{
public static void Main(){
ArrayList a = new ArrayList();
Point p; // 分配一个Point(不在堆中分配)
for(Int32 i =0;i <10;i++){
p.x =p.y =i; // 初始化值类型的成员
a.Add(p); // 对值类型进行装箱,并将引用添加到ArrayList中
}
}
}
每一次循环迭代,都会初始化一个Point的值类型字段(x和y)。然后,这个Point会存储到ArrayList中。但让我们思考一下。ArrayList中究竟存储的是什么?是Point结构,Point结构的地址,但是其他完全不同的东西?要知道正确答案,必须研究ArrayList的Add方法。
为了将值类型转换成一个引用类型,要使用一个名为装箱(boxing)的机制。下面总结了对值类型的一个实例进行装箱操作时在内部发生的事情。
1,在托管堆中分配好内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量。
2,值类型的字段复制到新分配的堆内存。
3,返回对象的地址。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。
拆箱不是直接将装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。事实上,指针指向的是已装箱实例中的未装箱部分。所以,和装箱不同,拆箱不要求在内存中复制任何字节。知道这个重要的区别之后,还应知道的一个重点在于,往往会紧接着拆箱操作发生一次字段的复制操作。
显然,装箱和拆箱/复制操作会对应用程序的速度和内存消耗产品不利影响,所以应该注意编译器在什么时候生成代码来自动这些操作,并尝试手动编写代码,尽量避免自动生成代码的情况。