C# 装箱 (boxing) 和拆箱 (unboxing)_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > C# 装箱 (boxing) 和拆箱 (unboxing)

C# 装箱 (boxing) 和拆箱 (unboxing)

 2015/3/31 16:14:50  rizo  程序员俱乐部  我要评论(0)
  • 摘要:目录:1.装箱和拆箱2.深入理解装箱和拆箱3.int[]toobject[],值类型数组到对象数组的转化4.使用泛型减少装箱和拆箱1.装箱和拆箱装箱就是把“值类型”转换成“引用类型”;拆箱就是把“引用类型”转换成“值类型”;首先,我们要弄明白为什么需要装箱和拆箱。C#的所有类型,包括int、boo等,都继承自System.Object,但是却又有值类型和引用类型之分。这时你要问,int是继承自object类型的,object是引用类型,那为何int不是引用类型而是值类型的呢
  • 标签:C#

目录:

1. 装箱和拆箱

2. 深入理解装箱和拆箱

3. int[] to object[],值类型数组到对象数组的转化

4. 使用泛型减少装箱和拆箱

1.  装箱和拆箱

装箱 就是把“值类型”转换成“引用类型”;

拆箱 就是把“引用类型”转换成“值类型”;

        首先,我们要弄明白为什么需要装箱和拆箱。C#的所有类型,包括int、boo等,都继承自System.Object,但是却又有值类型和引用类型之分。这时你要问,int是继承自object类型的,object是引用类型,那为何int不是引用类型而是值类型的呢?这就涉及到装箱和拆箱的概念了。

         我们知道对象是创建在堆上的,它的创建和销毁必然带来额外的CPU和内存消耗。如果将int,boo等微小而常用的数据类型都放在堆上创建和销毁,语言的性能将会被极大的限制,有时甚至是无法忍受的。C#将值类型和引用类型分开,值类型直接在栈中被创建,超过作用域后直接销毁。当需要值类型成为对象时,使用装箱操作,让值类型变为一个引用类型的对象。这样,我们就可以使用object作为通用的接口统一语言内的一切类型。

        拆箱 MSDN官方文档里用的是 取消装箱。事实上拆箱是装箱的逆操作,也就是说我们只对装过箱的引用类型(通常是object对象)进行拆箱操作。单纯拆箱操作的后果无法设想的。

        装箱和拆箱是C#的核心概念,C#利用其完成类型系统的统一。有了装箱,任何类型的值都可以视为一个对象。CLR在装箱时是将值类型包装到System.Object的内部,再将其存储到托管堆上。拆箱是从对象中提取值类型。装箱是隐式的而拆箱是显示的。

class="brush: csharp; auto-links: true; collapse: false; first-line: 1; gutter: true; html-script: false; light: false; ruler: false; smart-tabs: true; tab-size: 4; toolbar: true;">//装箱 boxing
int i = 3 ;  //分配在栈上
object o = i ;//隐式装箱操作,int i 在堆上
object b = (object)i ; //显示装箱操作
//拆箱 unboxing
int j = (int) o ;//显示拆箱(将对象o拆箱为int类型)

int k = b ;//error!!, 不能隐式拆箱

拆箱 的操作包括

1,检查对象实例,以却确保它是给定值类型的装箱值。

2,将该值从实例复制到值类型变量中。

 

下面来看看这个例子

int i=0;
System.Object obj=i;
Console.WriteLine(i+","+(int)obj);

其中共发生了3次装箱和一次拆箱!^_^,看出来了吧?!
第一次是将i装箱,第2次是输出的时候将i转换成string类型,而string类型为引用类型,即又是装箱,第三次装箱就是(int)obj的转换成string类型,装箱!
拆箱就是(int)obj,将obj拆箱!!

2.  深入理解装箱和拆箱

object o = 1 ;

这句话的IL代码如下:

.locals init (

  [0] object objValue

  ) //以上三行IL表示声明object类型的名称为objValue的局部变量

  IL_0000: nop

  IL_0001: ldc.i4.s 1 //表示将整型数1放到栈顶

  IL_0003: box [mscorlib]System.Int32 //执行IL box指令,在内存堆中申请System.Int32类型需要的堆空间

  IL_0008: stloc.0 //弹出堆栈上的变量,将它存储到索引为0的局部变量中

 

注意注释的部分。执行装箱操作时不可避免的要在堆上申请内存空间,并将堆栈上的值类型数据复制到申请的堆内存空间上,这肯定是要消耗内存和cpu资源的。

object objValue = 4;

int value = (int)objValue;

同样,看看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

 

拆箱操作的执行过程和装箱操作过程正好相反,是将存储在堆上的引用类型值转换为值类型并给值类型变量。装箱操作和拆箱操作是要额外耗费cpu和内存资源的,所以在c# 2.0之后引入了泛型来减少装箱操作和拆箱操作消耗。

3. int[] to object[],值类型数组到对象数组的转化

我们不能直接把值类型的数组赋值给对象数组,例如:

 int[] array  = new int[] { 0 } ;
 object[] oiArray = (object[])array;//error!! 不能将int[] 转换到 object[]
 string[] a={"1","2","3"};
 object[] osArray = a ;//正确,a是引用类型数组,不存在装箱和拆箱

(object[])a无法将a所有的值类型对象“直接”转换为引用类型,所以编译器不会通过这个转换。可以使用如下的方式达到目的:

  int[] array  = new int[] { 0 } ;
  object[] oArray = new object[array.Length];
  for(int i =0 ; i< array.Length ; i++)
  {
     oArray[i] = array[i]; //隐式装箱
  }

 

4. 使用泛型减少装箱和拆箱

        有时说使用泛型能提高C#程序的性能,有一部分性能的提升是由减少了装箱和拆箱带来的。考察下面的代码:

public class Test
{
    object _o  ;
    
    public Test(object o)
    {
	_o = o ;
    }
}


public class Test<T>
{
    T _o ;
    public Test(T o)
    {
        _o = o ;
    }
}

        第一个Test类中没有使用泛型,如果将一个int类型的值传入Test,将会引发多次的装箱和拆箱操作。而泛型类在实例化时已经明确了类型,复制操作时就不会有装箱和拆箱操作了。

 

引用:

1. 玉开 http://www.cnblogs.com/yukaizhao/archive/2011/10/18/csharp_box_unbox_1.html

2. MSDN https://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx

发表评论
用户名: 匿名