值类型比引用类型“轻”,原因是它们不作为对象在托管堆中分配,不被垃圾回收,也不通过指针进行引用。
但很多时候都需要获取对值类型实例的引用。
创建ArrayList来容纳一组Point结构。
Add方法原型如下:
Add获取的是一个Object参数。也就是说,Add获取对托管堆上的一个对象的引用(或指针)来作为参数。
但是代码传递的是Point,值类型。为了使代码正确工作,Point值类型必须转换成真正的、在堆中托管的对象,而且必须获取对该对象的引用。
//--
将值类型转换成引用类型要使用装箱机制。下面总结了对值类型的实例进行装箱时所发生的事情:
1.在托管堆中分配内存。分配的内存量是值类型各字段所需的内存量,还要加上托管堆所有对象都有的两个额外成员(类型对象指针和同步块索引)所需的内存量。
2.值类型的字段复制到新分配的堆内存。
3.返回对象地址。现在该地址是对象的引用;值类型成了引用类型。
//--
C#编译器自动生成对值类型实例进行装箱所需的IL代码。
//--
泛型集合类,性能显著提升,不需要装箱/拆箱,托管堆中需要创建的对象减少了,降低垃圾回收次数,类型安全。
//--
拆箱
它获取ArrayList的元素0包含的引用(或指针),试图将其放到Point值类型的实例point中。
为此,已装箱的Point对象中的所有字段都必须复制到值类型变量point中,后者在线程栈上。CLR分两步完成复制。
第一步:获取已装箱Point对象中的各个Point字段的地址。这个过程称为拆箱(unboxing)。
第二步:将字段包含的值从堆复制到基于栈的值类型实例中。
//--
拆箱不是直接装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是获取指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。其实,指针指向的是已装箱实例中的未装箱部分。所以和装箱不同,拆箱不要求在内存中复制任何字节。直到这个重要区别之后,还应该知道的一个重点是,往往紧接着是拆箱发生一次字段复制。
//--
已装箱值类型实例在拆箱时,内部发生下面这些事情:
1.如果包含“对已装箱值类型实例的引用”的变量为null,抛出NullReferenceException异常。
2.如果引用的对象不是所需值类型的已装箱实例,抛出InvalidCastException异常。
拆箱时,只能转型为最初未装箱的值类型。
//--
大多数方法进行重载的唯一目的就是减少常用值类型的装箱次数。
//--
未装箱值类型比引用类型更“轻”。这要归结于以下两个原因:
1.不在托管堆上分配。
2.没有堆上的每个对象都有的额外成员:“类型对象指针”和“同步块索引”。
//--
使用接口更改已装箱值类型中的字段,在C#中,不用接口方法便无法做到。