一 装箱和拆箱的概念
装箱是将值类型转换为引用类型 ;
拆箱是将引用类型转换为值类型 ;
值类型:包括原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举 (enum) 、结构 (struct)。
引用类型:包括类、数组、接口、委托、字符串等,我们利用代码说明一下:
int n = 10; object obj = n; Console.WriteLine("装箱之前的数字为{0},装箱之后的数字为{1}",n,obj.ToString());//此处为装箱操作,将数值类型转换为object类型 int m = (int)obj; Console.WriteLine("拆箱之前的数字为{0},拆箱之后的数字为{1}", obj.ToString(),m);//此处为拆箱操作,将object类型转换为数值类型
二 为什么需要装箱?
一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。 另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。
三 拆箱和装箱的优缺点
装箱和拆箱虽然满足了两只类型之间的转换。但是从装箱的过程中不难看出,每次装箱时要在堆中new一个新的对象,当量特别大是肯定会大大影响程序的效率。所以,在应用中,我们应该尽量避免装箱操作。
我们用代码说明装箱与拆箱的效率:
ArrayList List = new ArrayList();//声明一个数组集合 //List<int> list = new List<int>(); //声明一个泛型集合 Stopwatch sw = new Stopwatch(); //用于测量运行时间 //00:00:01.4535918 //00:00:00.1996060 sw.Start(); for (int i = 0; i < 10000000; i++) { List.Add((object)i);//此处发生了装箱的操作 //list.Add(i); } sw.Stop(); Console.WriteLine(sw.Elapsed);
上面的代码是装箱的操作:
下面是普通的操作:
//ArrayList List = new ArrayList();//声明一个数组集合 List<int> list = new List<int>(); //声明一个泛型集合 Stopwatch sw = new Stopwatch(); //用于测量运行时间 //00:00:01.4535918 //00:00:00.1996060 sw.Start(); for (int i = 0; i < 10000000; i++) { //List.Add((object)i);//此处发生了装箱的操作 list.Add(i); } sw.Stop(); Console.WriteLine(sw.Elapsed);
通过上面我们可以看到装箱与拆箱的时间对比,了解了装箱和拆箱的操作,我们可以清楚的明白:装箱操作会导致数据在堆和栈上进行拷贝,频繁的装箱操作会性能损失。而相比而言拆箱过程对性能损耗还是比较小的。
四 装箱与拆箱的过程
我们先看装箱时都会发生什么事情,下面是一行最简单的装箱代码
object obj = 1;
这行语句将整型常量1赋给object类型的变量obj; 众所周知常量1是值类型,值类型是要放在栈上的,而object是引用类型,它需要放在堆上;要把值类型放在堆上就需要执行一次装箱操作。
这行语句的IL代码如下,请注意注释部分说明:
locals init ( [0] object objValue ) //以上三行IL表示声明object类型的名称为objValue的局部变量 IL_0000: nop IL_0001: ldc.i4.s 9 //表示将整型数9放到栈顶 IL_0003: box [mscorlib]System.Int32 //执行IL box指令,在内存堆中申请System.Int32类型需要的堆空间 IL_0008: stloc.0 //弹出堆栈上的变量,将它存储到索引为0的局部变量中
以上就是装箱所要执行的操作了,执行装箱操作时不可避免的要在堆上申请内存空间,并将堆栈上的值类型数据复制到申请的堆内存空间上,这肯定是要消耗内存和cpu资源的。我们再看下拆箱操作是怎么回事:
请看下面的C#代码:
object objValue = 4; int value = (int)objValue;
上面的两行代码会执行一次装箱操作将整形数字常量4装箱成引用类型object变量objValue;然后又执行一次拆箱操作,将存储到堆上的引用变量objValue存储到局部整形值类型变量value中。
同样我们需要看下IL代码:
.locals init ( [0] object objValue, [1] int32 'value' ) //上面IL声明两个局部变量object类型的objValue和int32类型的value变量 IL_0000: nop IL_0001: ldc.i4.4 //将整型数字4压入栈 IL_0002: box [mscorlib]System.Int32 //执行IL box指令,在内存堆中申请System.Int32类型需要的堆空间 IL_0007: stloc.0 //弹出堆栈上的变量,将它存储到索引为0的局部变量中 IL_0008: ldloc.0//将索引为0的局部变量(即objValue变量)压入栈 IL_0009: unbox.any [mscorlib]System.Int32 //执行IL 拆箱指令unbox.any 将引用类型object转换成System.Int32类型 IL_000e: stloc.1 //将栈上的数据存储到索引为1的局部变量即value
今天就先说这么多了,有些知识是参考网络上的,希望我们一起进步!