装箱(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;
6
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 }
6
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 }
7
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的性能要好。