• 基础系列(4)—— C#装箱和拆箱


    一 装箱和拆箱的概念

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

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

    值类型:包括原类型(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

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

  • 相关阅读:
    Dubbo源码解析(四)之provider暴露篇
    Dubbo源码解析(六)之provider调用篇
    Dubbo源码解析(九)之consumer调用篇
    Dubbo源码解析(八)之consumer关联provider
    Dubbo源码解析(七)之consumer初始化
    Dubbo源码解析(一)之配置解析篇
    MXNet转Onnx出现错误AttributeError: No conversion function registered for op type SoftmaxActivation yet. AttributeError: No conversion function registered for op type UpSampling yet.
    druid和druid-spring-boot-starter 的区别
    AOP@Before,@After,@AfterReturning,@AfterThrowing执行顺序
    NSInvocation
  • 原文地址:https://www.cnblogs.com/wyh19941210/p/5847086.html
Copyright © 2020-2023  润新知