值类型的装箱和拆箱

在CLR中为了将一个值类型转换成一个引用类型,要使用一个名为装箱的机制。

  下面总结了对值类型的一个实例进行装箱操作时内部发生的事:

  1)在托管堆中分配好内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆上的所有对象都有的两个额外成员(类型对象指针同步块索引)需要的内存量。

  2)值类型的字段复制到新的分配的堆内存。

  3)返回对象的地址。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。

  拆箱不是直接将装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是一个获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。事实上,指针指向的是已装箱实例中的未装箱部分。所以,和装箱不同,拆箱不要求在内存中复制字节。还有一个重点就是,拆箱之后,往往会紧接着发生一次字段的复制操作。

  一个已装箱的值类型实例在拆箱时,内部会发生下面这些事情。

  1.如果包含了"对已装箱值类型实例的引用"的变量为null,就抛出一个NullReferenceException异常。

  2.如果引用指向的对象不是所期待的值类型的一个已装箱实例,就抛出一个InvalidCastException异常。

  上面第二条意味着一下代码不会如你预期的那样工作:

public static void Main(){
    Int32 x = 5;
    Object o = x;
    Int16 y = (Int16) o;//抛出异常
}

  在对一个对象进行拆箱的时候,只能将其转型为原始未装箱时的值类型——Int32,下面是正确的写法:

public static void Main(){
    Int32 x = 5;
    Object o = x;             //对x进行装箱,o引用已装箱的对象 
    Int16 y = (Int16) (Int32) o; //先拆箱为正确的类型,在进行装箱
}

  前面说过,在进行一次拆箱后,经常会紧接着一次字段的复制。以下演示了拆箱和复制操作:

public static void Main() {
    Point p = new Point(); //栈变量
    p.x = p.y = 1;
    object o = p;          //对p进行装箱,o引用已装箱的实例
    p = (Point) o;         //对o进行拆装,将字段从已装箱的实例复制到栈变量
}

  在最后一行,C#编译器会生成一条IL指令对o进行拆箱,并生成另一条IL指令将这些字段从堆复制到基于栈的变量p中。

  再看看一下代码:

public static void Main() {
    Point p = new Point();      // 栈变量
    p.x = p.y = 1;
    object o = p;               // 对p进行装箱,o引用已装箱的实例

    // 将Point的x字段变成2
    p = (Point) o;              // 对o进行拆装,将字段从已装箱的实例复制到栈变量
    p.x = 2;                    // 更改变量的状态
    o = p;                      // 对p进行装箱,o引用已装箱的实例
}    

  最后三行代码唯一的目的就是将Point的x字段从1变成2.为此,首先要执行一次拆箱,在执行一次字段复制,在更改字段(在栈上),最后执行一次装箱(从而在托管堆上创建一个全新的已装箱实例)。希望你能体会到装箱和拆箱/复制操作对应用程序性能的影响。

  在看个演示装箱和拆箱的例子:  

private static void Main(string[] args)
{
     Int32 v = 5;            // 创建一个伪装箱的值类型变量
     Object o = v;           // o引用一个已装箱的、包含值5的Int32
     v = 123;                // 将未装箱的值修改成为123
     Console.WriteLine(v + "," + (Int32)o);  //显示"123,5"
}

  你可以看出上述代码进行了几次装箱操作?如果说是3次,你会不会意味呢?我们来看下生成的IL代码。  

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    //代码大小
    .maxstack 3
    .locals init (
        [0] int32 num,
        [1] object obj2)
    L_0000: nop 

    // 将5加载到v中
    L_0001: ldc.i4.5
    L_0002: stloc.0 

    // 对v进行装箱,将引用指针存储到o中
    L_0003: ldloc.0
    L_0004: box int32
    L_0009: stloc.1 

    // 将123加载到v中
    L_000a: ldc.i4.s 0x7b
    L_000c: stloc.0 

    // 对v进行装箱,并将指针保留在栈上以进行Concat(连接)操作
    L_000d: ldloc.0
    L_000e: box int32

    // 将字符串加载到栈上以执行Concat操作
    L_0013: ldstr ","

    // 对o进行拆箱:获取一个指针,它指向栈上的Int32的字段
    L_0018: ldloc.1
    L_0019: unbox.any int32

    // 对Int32进行装箱,并将指针保留在栈上以进行Concat(连接)操作
    L_001e: box int32

    // 调用Concat
    L_0023: call string [mscorlib]System.String::Concat(object, object, object)
    // 将从Concat放回的字符串传给WriteLine
    L_0028: call void
[mscorlib]System.Console::WriteLine(string)
    L_002d: nop 

    // 从Main返回
    L_002e: ret
}

  提示:主要原因是在Console.WriteLine方法上。

  Console.WriteLine方法要求获取一个String对象,为了创建一个String对象,C#编译器生成的代码来调用String对象的静态方法Concate。该方法有几个重载的版本,唯一区别就是参数数量,在本例中需要连接三个数据项来创建一个字符串,所以编译器会选择以下Concat方法来调用:

public static String Concat(Objetc arg0, Object arg1, Onject arg2);

  所以,如果像下面写对WriteLine的调用,生成的IL代码将具有更高的执行效率:

Console.WriteLine(v + "," + o);  //显示"123,5"

  这只是移除了变量o之前的(Int32)强制转换。就避免了一次拆箱和一次装箱。

  我们还可以这样调用WriteLine,进一步提升上述代码的性能:

Console.WriteLine(v.ToString() + "," + o);  //显示"123,5"

  现在,会为未装箱的值类型实例v调用ToString方法,它返回一个String。String类型已经是引用类型,所以能直接传给Concat方法,不需要任何装箱操作。

  下面在演示一个装箱和拆箱操作:  

private static void Main(string[] args)
{
     Int32 v = 5;                  // 创建一个伪装箱的值类型变量
     Object o = v;                // o引用一个已装箱的、包含值5的Int32
     v = 123;                       // 将未装箱的值修改成为123
     Console.WriteLine(v)   //显示"123"
     v = (Int32) o;               //拆箱并将o复制到v
     Console.WriteLine(v);  //显示"5"
}

  上述代码发生了多少次装箱呢?答案是一次。因为System.Console类定义了获取一个Int32作为参数的WriteLine方法的重载版本:  

public static String Concat(Int32 value);

  在WriteLine方法内部也许会发生装箱操作,但这已经不是我们能控制的。我们已经尽可能地从自己的代码中消除了装箱操作。

时间: 2024-10-19 06:26:42

值类型的装箱和拆箱的相关文章

值类型的装箱与拆箱浅析

值类型的装箱与拆箱浅析 2012-02-23 15:47 by 秋梧, ... 阅读, ... 评论, 收藏, 编辑 阅读目录 前言 值类型的装箱 值类型的拆箱 装箱和拆箱实例 结束语 前言 在.Net 中值类型向引用类型的转换以及从引用类型到值类型的转换是需要装箱(boxing)和拆箱(unboxing)的,这是因为值类型是比引用类型更轻型的一种类型,因为他们不想对象那样在托管队中分配,不会被GC收集,而且不需要通过指针来引用.但是在许多情况下都需要获取对值类型的一个实例的引用.对于在值类型与

CLR via 笔记 5.3 值类型的装箱和拆箱

1.装箱 为了将一个值类型转换成一个引用类型,要使用一个名为装箱(Boxing)的机制. 1.在托管堆中分配好内存.分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量. 2.值类型的字段复制到新分配的堆内存. 3.返回对象的地址.现在,这个地址是对一个对象的引用,值类型现在是一个引用类型. 2.拆箱 包含在已装箱对象中的所有字段都必须复制到值类型变量中,后者在线程栈上.CLR分两步完成这个复制操作. 第一步是获取已装箱的对象中

[CLR via C#]值类型的装箱和拆箱

我们先来看一个示例代码: namespace ConsoleApplication1 { class Program { static void Main(string[] args) { ArrayList a = new ArrayList(); Point p; for (int i = 0; i < 10; i++) { p.x = p.y = i; a.Add(p); } Console.ReadKey(); } } struct Point { public Int32 x, y;

读经典——《CLR via C#》(Jeffrey Richter著) 笔记_值类型的装箱和拆箱(一)

[值类型在装箱过程中内部发生的事情] 1. 在托管堆中分配好内存.分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步快索引)需要的内存量. 2.值类型的字段复制到新分配的堆内存. 3.返回对象的地址.现在,这个地址是对一个对象的引用,值类型现在是一个引用类型.

c# 的引用类型和值类型和数据的拆箱和装箱

c#中引用类型和值类型的区分: 一般的以calss声明的变量的类型是引用类型的,引用类型是存放到内存的堆上存放的是数据的地址.值类型是像int float 还有struct等属于值类型的数据类型,值类型的数据是存放在堆栈上的存放的数据本身. 拆箱和装箱: 我们可以用一个例子来理解装箱和拆箱的关系和作用.我们都知道小时候在村里有哪种你给他一些铝制品他可以给你溶成一个你想要的其他的铝制器件.装箱和拆箱就是这个原理.例如:你想把一种数据类型转化为另一种数据类型,int16到int32的数据类型的转化,

Java中Integer与int类型的装箱和拆箱

其实Integer与int类型的赋值与比较最关键的一点就是:这两个变量的类型不同.Integer是引用类型,int是原生数据类型.         我们分四种情况来讨论:         1) Integer与int类型的赋值                 a.把Integer类型赋值给int类型.此时,Integer类型变量的值会自动拆箱成int类型,然后赋给int类型的变量,这里底层则是通过调用intValue()方法来实现所谓的拆箱的.                 b.把int类型赋

转 C# 装箱和拆箱[整理]

1.      装箱和拆箱是一个抽象的概念 2.      装箱是将值类型转换为引用类型 :拆箱是将引用类型转换为值类型       利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来 例如: int val = 100; object obj = val; Console.WriteLine (“对象的值 = {0}", obj); 这是一个装箱的过程,是将值类型转换为引用类型的过程 int val = 100; object obj =

C# 装箱和拆箱[整理](转)

//1. // 装箱和拆箱是一个抽象的概念 //2. // 装箱是将值类型转换为引用类型 :拆箱是将引用类型转换为值类型 // 利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将//值类型与引用类型链接起来 //例如: int val = 100; object obj = val; Console.WriteLine ("对象的值 = {0}", obj); //这是一个装箱的过程,是将值类型转换为引用类型的过程 int val = 100; objec

【转载】C# 装箱和拆箱[整理]

1.      装箱和拆箱是一个抽象的概念 2.      装箱是将值类型转换为引用类型 :拆箱是将引用类型转换为值类型       利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来 例如: int val = 100; object obj = val; Console.WriteLine (“对象的值 = {0}", obj); 这是一个装箱的过程,是将值类型转换为引用类型的过程 int val = 100; object obj =