【值类型在装箱过程中内部发生的事情】
1. 在托管堆中分配好内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步快索引)需要的内存量。
2.值类型的字段复制到新分配的堆内存。
3.返回对象的地址。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。
【值类型在拆箱过程中内部发生的事情】
struct Point { public Int32 x, y; } public sealed class Program { public static void Main() { ArrayList a = new ArrayList(); Point p = new Point(); p.x=1; p.y=2; a.Add(p);//发生装箱
p = (Point)a[0];//发生拆箱 } }
CLR 分 两步 完成这个复制操作。
1. 获取已装箱的 Point 对象中的各个 Point 字段的地址。这个过程就是 拆箱。
2. 将这些字段包含的值从堆中复制到基于栈的值类型实例中。
拆箱不是直接将装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。事实上,指针指向的是已装箱实例中的未装箱部分。所以,和装箱不同,拆箱不要求在内存中复制任何字节。知道这个重要的区别之后,还应该知道的一个重点在于,往往会紧接着拆箱操作发生一次字段的复制操作。
一个已装箱的值类型实例在拆箱时,内部会发生下面这些事请:
1. 如果包含了“对已装箱值类型实例的引用”的变量为null,就抛出一个NullReferenceException异常。
2. 如果引用指向的对象不是所期待的值类型的一个已装箱实例,就抛出一个InvalidCastException异常。
上述第二条意味着以下代码不会如你可能预期的那样工作:
public static void Main() { Int32 x = 5; Object o = x; //对x进行装箱,o引用已装箱的对象 Int16 y = (Int16) o;//抛出一个InvalidCastException异常 }
从逻辑上说,完全可以获取o所引用一个已装箱的Int32,然后将其强制转换为一个Int16.然而,在对一个对象进行拆箱的时候,只能将其转型为原未装箱的值类型——本例即为Int32。下面是上述代码正确的写法:
public static void Main() { Int32 x = 5; Object o = x; //对x进行装箱,o引用已装箱的对象 Int16 y = (Int16)(Int32)o;//先拆箱为正确的类型,再进行转型 }