装箱与拆箱

装箱(boxing):将值类型转换为引用类型。

拆箱(unboxing):将引用类型转换为值类型。

c#数据类型分:值类型【简单类型(布尔类型 字符类型 实数类型) 结构类型struct  枚举类型enum】

        引用类型【接口类型interface  所有的数组 类类型class 委托delegate】  指针类型

为何需要装箱? 
一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需

要将一个值类型(如Int32)传入时,需要装箱。 
另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据

加入容器时,需要装箱。

 

装箱的内部操作。 
装箱: 对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。 
  第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。 
  第二步:将值类型的实例字段拷贝到新分配的内存中。 
  第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。 
拆箱:检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量

中。

 

 

在拆箱的过程中要注意以下两点:

1. 如果对已装箱的值类型的引用的变量为null,会引发NullRefreenceException异常

2. 如果一个引用指向的对象在拆箱时不是用的装箱时所使用的类型,将会引发InvalidCastException异常。代码如下:

 

1 static void Main(string[] args)
2 {
3 Int32 x = 5; 
4 Object o = x; 
5 Int16 y = (Int16)o; //引发InvalidCastException异常
6 }
7


正确的做法是,现将其用Int32类型来拆箱,然后再强制转换为Int16

 

1 static void Main(string[] args)
2 {
3 Int32 x = 5; 
4 Object o = x; 
5 Int16 y = (Int16)(Int32)o; 
6 }
 

下面来看两段程序来深入理解下装箱和拆箱

代码一:

1 static void Main(string[] args)
2 {
3 Int32 x = 5; 
4 Object o = x; 
5 x = 123; 

7 Console.WriteLine(x + ", " + (Int32)o); 
8 }
9


上面的代码中有多少次装箱呢?乍一看好像就一次(Object o=x;),其实一共有三次装箱,看看IL代码就一目了然了。

 

程序的执行步骤:

 

1 创建一个Int32的未装箱的值类型实例x,并初始化为5.

2 创建Object类型的变量o,并指向x。由于引用类型的变量必须要执行堆中的对象,所以要对x进行装箱(第一次装箱),并将x在堆中的引用地址存储在o中。

3 将值123赋给未装箱的值类型实例x中。

4 调用WriteLine方法,WriteLine方法的参数值类型为String,现在WriteLine方法存在三个数据项,值类型x、string类型“,”和一个已装箱的Int32类型实例的引用o,这三个数据项必须要合并成一个string对象才能被调用。

5 调用String对象的静态方法Concat,Concat方法有9个重载,根据那三个数据项会选择下面方法执行。

 

 

6 第一个参数arg0传入的是x ,参数类型为object,所以要对x进行装箱(第二次装箱),将引用地址传给arg0,arg1传入的是字符串“,”,字符串就是引用类型,直接传引用地址,arg2传入的是将o拆箱然后再装箱(第三次装箱)的引用地址传入。

 

上面代码中的WriteLine方法如果直接写成Console.WriteLine(x + ", " + o); 将会有跟高的相率,因为o本身就是Object类型,在Concat的时候不用进行装箱拆箱。

 

 

代码二:看看这段程序发生了几次装箱

 

1 static void Main(string[] args)
2 {
3 Int32 x = 5; 
4 Object o=x; 
5 x=123; 
6 Console.WriteLine(x); 
7 x = (Int32)o; 
8 Console.WriteLine(x); 
9 Console.WriteLine(o); 
10 }
11


上面的代码只发生了一次装箱,因为WriteLine方法的重载版本中参数类型可以为Objet或是Int32,在调用WriteLine方法是并没有装箱,唯一的一次装箱是Object o=x; 。

 

代码三:

1 static void Main(string[] args)
2 {
3 Int32 x = 5; 
4 CheckRef(x, x); //输出不同引用
5 }

7 static void CheckRef(object obj1, object obj2)
8 {
9 if (obj1 == obj2)
10 Console.WriteLine("相同引用"); 
11 else
12 Console.WriteLine("不同引用"); 
13 }
14

 

1 static void Main(string[] args)
2 {
3 Int32 x = 5; 
4 Object o = x; 
5 CheckRef(o,o); //输出相同引用
6 }

8 static void CheckRef(object obj1, object obj2)
9 {
10 if (obj1 == obj2)
11 Console.WriteLine("相同引用"); 
12 else
13 Console.WriteLine("不同引用"); 
14 }
15

执行上面代码将发生两次装箱,因为CheckRef方法的两个参数都是Object类型,传入的都是值类型的实例,可以讲代码改进下,先将x转换成Object类型再传入方法,如下:

 

改进后只进行一次装箱操作了,效率提高了,但是会发现运行的结果页发生了变化,所以这种做法在有些时候是很危险的。

装箱拆箱操作极大的破环程序的性能,不过在Net2.0中提供了泛型集合类,所以完全可以用List 和Dictionary 来代替 原来1.0中的ArrayList和HashTable,即使是List也会比ArrayList的性能要好。

posted @ 2014-10-31 16:30  怪咖Eric  阅读(177)  评论(0编辑  收藏  举报