读CLR via C#总结(4) 值类型的装箱和拆箱
值类型是比引用类型更"轻型"的一种类型,因为它们不作为对象在托管堆中分配,不会被垃圾回收,也不通过指针来引用。但在许多情况下,都需要获取对值类型的一个实例引用。为了将一个值类型转换成一个引用类型,要使用一个名为装箱(boxing)的机制。
一,装箱
装箱即将一个值类型转换成一个引用类型,下面是总结对值类型的一个实例进行装箱时内部发生的事情:
1,在托管堆中分配内存。内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量。
2,将值类型的字段复制到新分配的堆内存。
3,返回对象的地址(或称指针)。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。
例如:代码如下:
namespace ValueTypeDemo2
{
//声明一个值类型
struct Point
{
public int x, y;
}
class Program
{
static void Main(string[] args)
{
ArrayList a = new ArrayList();
Point p;//分配一个Point(不在堆中分配)
for (int i = 0; i < 10; i++)
{
p.x = p.y = i;//初始化值类型中的成员
a.Add(p);//对值类型进行装箱,并将引用添加到ArrayList中,因为Add()方法需要的是一个Object对象
}
}
}
}
二,拆箱
拆箱实际上就是获取一个指向包含在一个对象中的原始值类型(数据字段)的指针,往往会紧接着拆箱操作后发生一次字段复制的操作,将堆中的字段复制到基于栈的值类型的实例中。
例如:代码如下:
Point p=(Point)a[0];
CLR实际上分两步完成这个复制操作:
1,获取已装箱的Point对象中的各个Point字段的址,这个过程称为拆箱(unboxing)。
2,将这些字段包含的值从堆中复制到基于栈的值类型的实例中。注意,这个过程不属于拆箱的范围。
下面是一个已装箱值类型实例在拆箱时内部发生的事情:
1,如果包含"对已装箱值类型实例的引用"的变量为Null,就抛出一个NullReferenceException异常。
2,如果引用指向的对象不是期待的值类型的一个已装箱实例,就抛出一个InvalidCastException异常。
3,返回一个指向包含在一个对象中的原始值类型的指针。
下面是一个综合实例,代码如下:
namespace ValueTypeDemo3
{
//声明一个值类型
struct Point
{
public int x, y;
}
class Program
{
static void Main(string[] args)
{
Point p;//创建Point的实例,在栈上分配
p.x = p.y = 1;//初始化值类型的成员
object o = p;//对p进行装箱,o引用已装箱的实例
p = (Point)o;//对o进行拆箱,将字段从已装箱的实例中复制到栈变量中
}
}
}
生成的IL代码如下图,可以看到程序发生了一次装箱和一次拆箱:
TAG: