CLR IN C# 笔记 ——装箱和拆箱
值类型比引用类型轻,原因是他们不作为对象在托管堆中分配,不被垃圾回收,也不通过指针进行引用,但有时候需要获取对值类型的实例引用
装箱:
struct Point { public int x,y; } public class Program { public void main() { ArrayList a=new ArrayList(); Point p = new Point(); p.x = p.y = 1; a.add(p); } }
PS:上述代码没有任何意义,因为自从有了泛型后ArrayList就很少有人用了。只是ArrayList类中的Add方法很有代表性,所以用他来说明
查看ArrayList中的Add方法原型:
public virtual int32 add(Object value);
add获取的是一个Object参数,这是一个引用类型参数,但代码中传递的是一个值类型引用,为了时代码正取工作,Point值类型必须转换为托管堆上的一个对象,而且必须获取该对象的引用。
将值类型转换为引用类型主要干了这么几件事:
1.在托管堆中分配内存,分配的内存量是值类型各字段所需要的内存量,还要加上托管堆所有对象都有的两个额外成员(类型对象指针和同步索引快)所需的内存量。
2.值类型的字段复制到新分配的堆内存。
3.返回对象地址。现在该地址是对象引用;值类型成了引用类型(没有看懂这句话,什么叫值类型成了引用类型?通过装箱步骤可以发现这其实是一个复制的动作。p这个结构本身还是存在的。)
已装箱的Point对象的地址返回并传给Add方法。Point对象一直存在于堆中,直到被垃圾回收,Point值类型变量p可被重用,因为ArrayList不知道关于他的任何事情。这种情况下,已装箱质值类型的生存周期超过了未装箱的值类型的生存周期。
拆箱:
Point p = (Point)a[0];
获取ArrayList中的第0个元素包含的引用,试图将其Point值类型的实例p中。为此已装箱的Point对象所有的字段都需要复制到值类型p中。
主要包含了两个过程:
1.获取已装箱Point对象中的各个Point字段的地址。这个过程称为拆箱
2.将字段包含的值复制到基于栈中的值类型实例中。
拆箱的代价要小的多,拆箱实际是获取指针的过程
拆箱会导致的问题
1.如果包含“对已装箱值类型实例的引用”的变量为NULL(就是a[0] 等于null的意思,翻译的真拗口)将抛出空指针异常。NullReferenceException
2.如果引用的对象不是所需要的值类型的已装箱实例,抛出InvalidCastException异常
不管是值类型也好还是引用类型也好,他们归根都是从System.Object中继承下来的。所以即使是值类型也会有下面的几个方法
GetType();ToString();Equals();
如果值类型没有重写这些方法那么必将执行装箱操作,因为这些方法由Object定义,要求this实参是指向托管堆中的对象。
如果重写了其中任何一个虚方法,那么CLR将以非虚地调用该方法,因为值类型隐士密封,不可能有类型从他派生。而且调用方法的值类型实例没有装箱
浙公网安备 33010602011771号