装箱和拆箱

装箱和拆箱

值类型变量在线程堆栈上分配存储空间,然而由于其派生自Object类,所以可以用一个Object类变量存放一个值类型数据。请看以下代码:

int i = 123;

object o = i;

很明显,第2句代码将值类型的数据“123”放到了一个Object类型的变量o中,而o是一个引用类型变量,其引用的对象必须存活于托管堆中。为了解决这个问题,CLR将值类型的数据“包裹”到一个匿名的托管对象中,并将此托管对象的引用放在Object类型的变量o中,这个过程称为“装箱(Boxing)”,装箱后对象的内存布局如图4-4所示。

事实上,CLR为所有的值类型变量都提供了一个对应的“装箱”数据类型,此数据类型其实是一个类,拥有与值类型相同的数据和行为。

需要注意的是,装箱之后的变量i与变量o是两个完全独立的变量,只是初值相同罢了,对任何一个变量值的改变不会影响另一个。

1   int i = 123;     

2   object o = i;        // 装箱:boxing

3   int j = (int) o;    // 拆箱:unboxing

上述第3句代码将装箱后的数据再“拆箱(Unboxing)”,将其值赋给变量j,参见图4-5。

“装箱”与“拆箱”使我们可以把值类型变量看成是引用类型变量,但这个操作是耗时的,会影响程序的运行性能,因此,应该尽量避免在程序中使用装箱与拆箱操作。

 深入内幕

装箱与拆箱的技术内幕

(1)装箱

装箱转换允许将值类型(Value Type)变量隐式转换为引用类型(Reference Type)变量。

将值类型变量的一个值装箱包括以下操作:分配一个对象实例,然后将值类型变量的值复制到该实例中。

可以用以下方法理解实际装箱过程:设想有一个特殊的装箱类(Boxing Class)。对任何值类型的类型T而言,装箱类的行为可描述如下:

sealed class T_Box: System.ValueType

{

    T value;

    public T_Box(T t)

    {

        value = t;

    }

}

下面的装箱语句:

int i = 123;

object box = i;

在概念上相当于

int i = 123;

object box = new int_Box(i);

实际上,像上面这样的T_Box和int_Box并不存在,并且装了箱的值的动态类型也不会真的属于一个类类型。相反,类型T的装了箱的值属于值类型T。例如:

int i = 123;

object box = i;

if (box is int) //装箱后的变量仍是int类型

{

    Console.Write("Box对象包含一个int型数据");

}

上述代码在运行时将在控制台上输出字符串“Box对象包含一个int型数据”。

装箱转换隐含着复制一份待装箱的值。这不同于从引用类型到Object类型的转换,在后一种转换中,转换后的值继续引用同一实例,只是将它当作派生程度较小的Object类型而已。例如,以下代码声明了一个值类型Point。

struct Point

{

    public int x, y;

    public Point(int x, int y)

    {

        this.x = x;

        this.y = y;

    }

}

则下面的语句

Point p = new Point(10, 10);

object box = p;

p.x = 20;

Console.Write(((Point)box).x);

将在控制台上输出值10,因为将p赋值给box是一个隐式装箱操作,它将复制p的值。但如果将Point声明为class,由于p和box将引用同一个实例,因此输出值为20。

(2)拆箱

拆箱是装箱的逆过程。

一个拆箱操作包括以下两个步骤:

* 检查对象实例是否为给定值类型的一个装了箱的值。

* 将该值从实例中复制出来。

参照前面的例子,下面的语句

object box = 123;

int i = (int)box; //拆箱

在概念上相当于

object box = new int_Box(123);

int i = ((int_Box)box).value;

为了使给定值类型拆箱转换在运行时取得成功,源操作数的值必须是对某个对象的引用,而该对象先前是通过将该值类型的某个值装箱而创建的。

q 如果源操作数为null,则将引发System.NullReferenceException异常。

q 如果源操作数是对不兼容对象的引用,则将引发System.InvalidCastException异常。

IL为装箱与拆箱分别准备了两条指令:Box和Unbox,读者可以查询Visual Studio 2005文档了解详情。



posted @ 2008-04-09 16:40  系咪噶  阅读(532)  评论(0编辑  收藏  举报