首页 > 代码库 > 复习系列之装箱和拆箱

复习系列之装箱和拆箱

一 装箱和拆箱的概念

装箱是将值类型转换为引用类型 ;

拆箱是将引用类型转换为值类型 ;

值类型:包括原类型(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: nopIL_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 = http://www.mamicode.com/4;
int value = http://www.mamicode.com/(int)objValue;

上面的两行代码会执行一次装箱操作将整形数字常量4装箱成引用类型object变量objValue;然后又执行一次拆箱操作,将存储到堆上的引用变量objValue存储到局部整形值类型变量value中。
同样我们需要看下IL代码:

 
.locals init (  [0] object objValue,  [1] int32 value) //上面IL声明两个局部变量object类型的objValue和int32类型的value变量IL_0000: nopIL_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

今天就先说这么多了,有些知识是参考网络上的,希望我们一起进步!

参考资料:www.jb51.net/article/37025.htm

复习系列之装箱和拆箱