FunctionGHW

立志成为真正程序员的码农,伪全栈,目前主要写C#

C#中值类型的装箱(boxing)和拆箱(unboxing)

FunctionGHW posted @ 2014年3月17日 17:57 in C#与.Net with tags c# 装箱 boxing 拆箱 unboxing , 1969 阅读

在C#中,把一个值类型转化成引用类型的过程称为装箱(boxing)。这个引用类型可以是object类型,也可以是此值类型实现的任何接口类型。

        int num = 5;
        //引起装箱操作
        object o = num;
        

CLR会把值类型包装在一个对象内部。装箱的过程大致如下:

  1. 像创建其他引用类型的实例一样,在托管堆上为新对象分配需要的内存。
  2. 将值类型的字段值复制到新对象的对应字段。
  3. 返回这个对象的引用。

拆箱(Unboxing)的过程简单些。拆箱就是从一个装箱后的对象里获得一个指针的过程,这个指针指向该对象中的值类型的字段。拆箱之后往往都伴随着字段的复制(比如赋值操作):

        int val = (int)o;
        

现在大家基本上都是说装箱/拆箱机制的缺点,其缺点至少有:

  • 装箱拆箱都需要额外的计算,影响性能。
  • 装箱需要消耗的托管堆内存,如果有大量的对象产生,会增加GC的压力。
  • 增加代码中强制类型转换操作的次数,增加出错的可能性。

一句话,装箱/拆箱操作对程序的运行速度和内存消耗都很不利,应该尽量避免

既然如此,这个机制为什么还要存在呢?所谓引用类型变量,其实是在线程栈上存放一个引用, 引用指向托管堆上的某个对象。假设这样一个场景,有一个方法的一个参数的类型是object, 也就是对任何类型都通用,object是引用类型,所以调用时就需要传递一个引用。 值类型也是object的分支,所以值类型也可以作为object才对,但是值类型的值是在线程栈上的, 因此为了保证代码能正常工作,就需要把这个值类型"放"到堆上,变成引用类型。

这样看来,所有含有object类型的参数的方法, 都存在引起装箱操作的可能。例如:

        ArrayList ary_lst = new ArrayList();
        ary_lst.Add(2014);// Add(object), 引起装箱
        

感觉上面的示例太过鸡肋,我们换一个。字符串的拼接操作大家用的很多吧, 估计有很多人和我一样随手就用起来了。

        int num = 5;
        string str = "the number is" + num;
        

连接操作会调用System.String.Concat()方法, 最匹配的一个重载是Concat(object, object)num是值类型,因此引起装箱操作,然后Concat方法内部会调用两个对象的ToString()方法,得到两个string,最后调用另一个重载方法Concat(string, string)。装箱操作所产生的"临时对象",对我们一点用也没有, 我们只是想要字符串"5"而已,所以可以这样做:

        int num = 5;
        //不会引起装箱
        string str = "the number is" + num.ToString();
        

因为操作数都是sting类型, 所以会直接调用Concat(string, string),既没有引起不必要的装箱,又少拐了一个弯。

String.Format()方法也存在这样的问题。

        //会引起装箱
        string str2 = string.Format("The number is {0}", 9);
        //不会引起装箱
        string str3 = string.Format("The number is {0}", 9.ToString());
        

 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter