提问:
首先我们来提两个疑问,我们自定义了一个类如Customclass类型,
Customclass myclass=new Customclass()
Object obj=myclass;
运行上面这段代码,我们会进行装箱操作吗?
基础知识:
.Net的类型分为两种,一种是值类型(Value Type ),另一种是引用类型(Reference Type)。这两个类型的本质区别,值类型数据是分配在栈中,而引用类型数据分配在堆上。那么如果要把一个值类型数据放到堆上,就需要装箱操作;反之,把一个放在堆上的值类型数据取出来,则需要进行拆箱操作。
说到具体程序代码上, 我们可以这样总结: 装箱是将值类型转换为引用类型 ;拆箱是在已装箱的前提下将引用类型转换为值类型.
我们要充分的了解装箱和拆箱,首先我们先得了解一下.net framework平台的类的构造.在这个平台上有一个万物之源,那就是System.Object类型,在之下系统又分了值类型(Value Type )和引用类型(Reference Type).值类型包括原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct),引用类型包括:类、数组、接口、委托、字符串等。所以到这里我们就可以得出一个结论,文章开头提的那个问题的答案也就不言而喻了.我们自定义声明的类型是一种引用类型,所以我们把他放到object里面的时候,是不会发生装箱操作的.
具体操作:
下面我们来解析一下装箱和拆箱系统做了那些操作
前面我们已经说到.值类型数据是分配在栈(stack)中,他是一个先进后出的结构,由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.而引用类型数据分配在堆(heap)上,其操作方式类似于数据结构中的栈。堆他是一种顺序随意的结构,一般由程序员分配释放若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。具体在.net框架上的情况你可以阅读 .NET中栈和堆的比较 这篇文章,这位博主翻译了四篇关于这方面的文章,为中国的IT视野做出了巨大的贡献啊.
我们来结合一个小例子说明一下装箱操作:
int i = 123; object o = (object)i;
对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。
第一步:新分配堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
第二步:将值类型的实例字段拷贝到新分配的内存中。
第三步:返回堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
我们通过这种方式将一个值类型的int i装箱为一个object类型的 o变量
拆箱就是一个逆过程了.将一个object 类型的o变量还原为int型的i变量,它进行了如下操作:
(1)环境须先判断堆栈上指向合法对象的地址,以及在对此对象向指定的类型进行转换时是否合法,如果不合法,就抛出异常;
(2)当判断类型转换正确,就返回一个指向对象内的值的指针。
针对上面的合法性判断,有两点需要我们注意:
(1)包含已装箱的值类型的引用的变量如果为null,就抛出一个NullReferenceException异常。
(2)如果引用指向的对象不是所要求的值类型的一个已装箱的实例,就抛出一个InvaildCastException异常
带来的影响:
我们可以从两个方面考虑:一个就是对于堆的操作效率比较低;另一个就是对于堆上分配的内存资源,需要GC来回收,从而降低程序效率。
显然,直观的来说.装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。
当然我们也可以直观的看出,拆箱所带来的性能损失是远小于装箱所带来的损失的.