四 装箱与拆箱
前面主要都讨论的是同类型直接的转换,引用到引用,值类型到值类型。还有一种用的非常多的就是引用类型和值类型之间的转换,比如传递参数时,值类型存储到一个引用类型时。也就是经常可以听到的装箱和拆箱操作。这确实是个非常复杂的地方。因为引用类型和值类型的内存空间是不同的,也就导致了许多性能问题和拷贝使用等问题。对于每一种值类型,运行库都提供一种相应的已装箱类型,这是与值类型有着相同状态和行为的类。当需要已装箱的类型时,某些语言要求使用特殊的语法(如C++要用关键字);而另外一些语言会自动使用已装箱的类型(C#是自动的)。在定义值类型时,需要同时定义已装箱和未装箱的类型。
- 由前面我们可以知道,值类型是分配在线程的堆栈上,而引用类型是分配在托管堆上的。所谓的‘装箱’也就是把值类型转化为一个引用类型的过程。实际上也就是把分配在堆栈上的对象,装箱(可能说打包比较形象,前面说过分配在托管堆的对象回又个附加成员),然后重新分配到托管堆上的。装箱操作通常由以下几步组成:
在托管堆上为新生成的引用类型分配内存空间。这个空间大小为值类型本身大小和附加成员(方法表指针和SyncBlockIndex) - 将值类型实例的字段拷贝到托管堆上新分配对象的内存中。
- 返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用。值类型实例也就变成了一个引用类型对象。
//声明一个值类型
struct Point
{
public Int32 x,y;
}
class app
{
static void Main()
{
ArrayList a = new ArraryList();
Point p ; //值类型分配到线程堆栈上
for(Int32 i = 0; i < 10 ;i++)
{
p.x = p.y = 1; //初始化值类型成员
a.Add(p); //Add(Object obj)方法要接受一个引用类型,所以会对p进行装箱操作。
}
}
}