值类型的拆箱和装箱

来源:互联网 发布:vue.js 左右滑动div 编辑:程序博客网 时间:2024/05/22 00:36

关于值类型的一个实例进行装箱操作时内部发生的事情如下:
1) 在托管堆中分配好内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量。
2)值类型的字段复制到新分配的堆内存。
3)返回对象的地址。这个地址是一个对象的引用,值类型现在是一个引用对象。
C# 编译器会自动生成对一个值类型的实例进行装箱所需要的IL代码,但你仍然需要理解内部发生的事情,否则很容易忽视代码长度和性能问题。
拆箱步骤如下:
1)获取已装箱的对象中各个字段的地址。
2)将这些字段包含的值从堆中复制到基于栈的值类型实例中。
举个例子如下:

using System.Collections.Generic;using System.Linq;using System.Text;namespace BoxAndUnBoxProject{    class Program    {        static void Main(string[] args)        {            Int32 v = 5;            Object o = v;//o引用一个已装箱的包含5的int32            v = 123;            Console.WriteLine(v+","+(Int32)o);        }    }}

从上述代码可以看出发生了3次装操作。第一次发生在Object o=v;第二次发生在v对object转换,第三次对(Int32)o 拆箱后在装箱。这才是真正的装箱,而不是所为的对v转换为ToString()装箱。
为了验证我的思想对不对,咱们先看下IL生成的代码,可以通过IL反编译器查看。如下:

using System.Collections.Generic;using System.Linq;using System.Text;namespace BoxAndUnBoxProject{    class Program    {        static void Main(string[] args)        {            Int32 v = 5;            string str = v.ToString();            //Object o = v;//o引用一个已装箱的包含5的int32            //v = 123;            //Console.WriteLine(v+","+(Int32)o);        }    }}

IL反编译代码如下:

.method private hidebysig static void  Main(string[] args) cil managed{  .entrypoint  // 代码大小       12 (0xc)  .maxstack  1  .locals init ([0] int32 v,           [1] string str)  IL_0000:  nop  IL_0001:  ldc.i4.5  IL_0002:  stloc.0  IL_0003:  ldloca.s   v  IL_0005:  call       instance string [mscorlib]System.Int32::ToString()   IL_000a:  stloc.1  IL_000b:  ret} // end of method Program::Main

上述代码转换string的时候没有发生装箱操作,因为木有box关键字。(不是所有的调用ToString()都不发生装箱操作,这里没有是因为Int32这个类override了ToString()方法,所以没有装箱操作。)
现在回归到第一次代码的反编译代码如下:

.method private hidebysig static void  Main(string[] args) cil managed{  .entrypoint  // 代码大小       47 (0x2f)  .maxstack  3  .locals init ([0] int32 v,           [1] object o)  IL_0000:  nop  IL_0001:  ldc.i4.5  IL_0002:  stloc.0  IL_0003:  ldloc.0  IL_0004:  box        [mscorlib]System.Int32  IL_0009:  stloc.1  IL_000a:  ldc.i4.s   123  IL_000c:  stloc.0  IL_000d:  ldloc.0  IL_000e:  box        [mscorlib]System.Int32  IL_0013:  ldstr      ","  IL_0018:  ldloc.1  IL_0019:  unbox.any  [mscorlib]System.Int32  IL_001e:  box        [mscorlib]System.Int32  IL_0023:  call       string [mscorlib]System.String::Concat(object,                                                              object,                                                              object)  IL_0028:  call       void [mscorlib]System.Console::WriteLine(string)  IL_002d:  nop  IL_002e:  ret} // end of method Program::Main

在IL_0004阶段 把Int32 v 装箱并加载到Object o 中,有box关键字。
看IL_0023阶段调用的是string.Concat(object,object,object) 方法,此时要对Int32 v 和(Int32)o 进行装箱操作,至于string “,” 它本身就是一个特殊的引用类型,而无需进行装箱操作。下图进行完整解析:

这里写图片描述这里写图片描述

这是完整的装箱和拆箱的过程 。所以对我们而言,能显示拆箱和装箱的就进行显示进行,这样更能提高效率,但是装箱和拆箱尽量少用,因为这玩意会占用大量的内存,对性能也有所影响。只有深刻理解了拆箱和装箱操作,才能更快、更轻松地构建高效率的应用程序。

那么下面装了几次箱:

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace BoxAndUnBoxProject{    class Program    {        static void Main(string[] args)        {            Int32 v = 5;            Object o = v;            Console.WriteLine(o.ToString());        }    }}
1 0
原创粉丝点击