New2011_装箱和拆箱 浅析深入

本文详细介绍了C#中的装箱与拆箱概念,包括它们的基本原理、执行过程及注意事项。通过具体示例解释了如何在代码中正确使用装箱与拆箱,避免性能损耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
C#中的数据类型包括值类型、引用类型和指针类型,而指针类型只有在不安全代码中使用。值类型包括简单类型、结构和枚举,引用类型包括类、接口、委托、数组和字符串等。为了保证效率,值类型是在栈中分配内存,在声明时初始化才能使用,不能为NULL,而引用类型在堆中分配内存,初始化时默认为NULL。 值类型超出作用范围系统自动释放内存,而引用类型是通过垃圾回收机制进行回收。由于C#中所有的数据类型都是由基类System.Object继承而来的,所以值类型和引用类型的值可以相互转换,而这转换过程也就是所谓的装箱和拆箱。

取消装箱是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:

  • 检查对象实例,确保它是给定值类型的一个装箱值。

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

以下语句同时说明了装箱和取消装箱操作:

int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;  // unboxing

 

下图显示了以上语句的结果。

取消装箱转换

取消装箱转换图形

要在运行时成功取消装箱值类型,被取消装箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创建的。尝试对 null 或对不兼容值类型的引用进行取消装箱操作,将导致InvalidCastException.

示例

下面的示例演示无效的取消装箱及引发的 InvalidCastException。使用 trycatch,在发生错误时显示错误信息。

class TestUnboxing
{
      static void Main()
      {
           int i = 123;
           object o = i;
            try
           {
               int j = (short) o;
               Console.WriteLine("OK");
           }
          catch(System.InvalidCastException e)
          {
              Console.WriteLine("Error");
          }
        finally      //不管如何Finally里的代码都是要执行的。所以是否加Finally 根据情况而定。这是面试里的问题。
         {
            Console.WriteLine("Over");
         }
      }
}
输出

Specified cast is not valid. Error: Incorrect unboxing.

如果将下列语句:

int j = (short) o;

更改为:

int j = (int) o;

将执行转换,并将得到以下输出:

Unboxing OK.

-----------------------------------------------------------------------------------------------

为了更深入的理解该知识点 我们从另一个层次面来补充下2011-09-09学习点

.NET 所有的类型都是基于CTS即Common Type System 公共类型系统 基类都为System.Object  然后就是两大类(指针类型除外)值类型和引用类型

值类型包括简单类型(int long  shor ....),结构(Struct) 枚举(Enum)

引用类型包括类,委托,接口,数组,字符串........

为了方便利用 或者说万物即对象万物都是相通的。此时便有了装箱和拆箱操作。

装箱 ----引用类型到值类型。

拆箱-----值类型到引用类型。

还是以一个面试题为例子来说明

            int i = 13;
            object ob = i;
            Console.WriteLine(i + "," + (Int32)ob);

上述代码执行了几次装箱和几次拆箱操作。

可以利用ILDASM来看下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       45 (0x2d)
  .maxstack  3
  .locals init ([0] int32 i,
           [1] object ob)                         //.Locals 定义了两个类型分别为int32 和object的局部变量
  IL_0000:  nop
  IL_0001:  ldc.i4.s   13                 //ldc是个指令,后面的i4.s指出作为32位(4个字节)整数被压入堆栈。而压入的值就是13.
  IL_0003:  stloc.0                         //stloc把上面的值从堆栈弹出给局部变量i,这里的.0是指弹出给到第一个局部变量中,也就是i了。
  IL_0004:  ldloc.0                        //这个值(13)被弹出后,就被装载回堆栈,也就是ldloc命令所为。
  IL_0005:  box        [mscorlib]System.Int32                  //使用CIL 装箱 BoX这个值为引用类型。
  IL_000a:  stloc.1                       //把box返回值弹出给第二个局部变量ob中。
  IL_000b:  ldloc.0                       
  IL_000c:  box        [mscorlib]System.Int32
  IL_0011:  ldstr      ","               //就是将字符串','压入堆栈

  IL_0016:  ldloc.1
  IL_0017:  unbox.any  [mscorlib]System.Int32
  IL_001c:  box        [mscorlib]System.Int32
  IL_0021:  call       string [mscorlib]System.String::Concat(object,
                                                              object,
                                                              object)
  IL_0026:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_002b:  nop
  IL_002c:  ret
} // end of method Program::Main

 

  (1)前面好说,跟前面一样 object ob = i;引起了一次装箱操作也就是IL_0005处代码。

    (2)后面可以看出Console.WriteLine方法调用的是单个String作为参数的版本。因此上面调用了String.Concat方法将i + "," + (Int32)ob这3个值连接产生单个String再传给WriteLine。
    (3)String.Concat的重载版本里面找到最匹配的就是Concat(object, object,object)。这样为了匹配这3个参数:
        (3.1) IL_000c处代码,第一个参数i被装箱
        (3.2)IL_0011 处ldstr      "," 就是将字符串','压入堆栈
        (3.3)然后 IL_0017 (int32)ob引起了一次拆箱操作
         (3.4)我们可怜的(int32)ob,又为了匹配Concat的参数,再次被装箱(IL_001c)
   明显后面那个(int32)ob造成了一次不必要的拆箱和装箱操作!所以正因为.net的自动类型处理能力,
还是小心地注意一下写法,否则就会引起不必有的性能损失。

 

备注看了一段的中间代码 上述有个红色大字号BOX命令 至于这个BOX命令执行了什么呢经网上查找如下:

   (1)在堆上分配内存。因为值类型最终有一个对象代表,所有堆上分配的内存量必须是值类型的大小加上容纳此对象及其内部结构(比如虚拟方法表)所需的内存量。
(2)值类型的值被复制到新近分配的内存中
(3)新近分配的对象地址被放到堆栈上,现在它指向一个引用类型

 

对比下列两个代码段 试着用ILDASM工具看看哪个性能好。

代码段一:

       int  i =13;

      System.Collection. ArrayList  arry1=new System.Collection.ArrayList();

      arry1.Add(i);

     Console.WriteLine("{0}",i);

 

代码段二:

     int i = 13;

    object ob =i;

    System.Collection. ArrayList  arry1=new System.Collection.ArrayList();

   array1.Add(ob);

    Console.WriteLine("{0}",ob);

 

 

 

答案:代码段一执行了2次Box  而二执行了一次Box.

 

基础点和深入点都讲了 下面我们会提出为何要用到装箱和拆箱呢,有经验的可以根据上面例子进行一个归纳譬如我们用的Console.WriteLine(Pa1,Pa2)一般这的类型都是Object啊 。可以任意找一些String的方法看看。

使用场景1:最普通的场景,调用一个含有类型为object的参数的方法,该object可支持任意类型,以便通用.当你需要将一个值类型传入时,需要装箱.

使用场景2:一个非泛型的容器,同样为了保证通用,而将元素类型定义为object,于是要将值类型数据加入容器是,需要装箱。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值