C++/CLI与C#中的装箱、拆箱的最大区别在于C++/CLI装箱后能得到强类型的对象,而C#则只能将值类型装箱为Object或ValueType,以下将先分别介绍两种语言的装箱拆箱操作,最活再进行综合的比较。
C#的装箱与拆箱
1. 最简单的装箱
Int 32 a = 100;
Object o = a;
产生的 IL 指令如下
IL_0001: ldc.i4.s 100
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: box [mscorlib]System.Int32
IL_ 000a : stloc.1
这里的装箱是显式的,有时则会进行隐式的装箱,例如
Int 32 a = 100;
Console.WriteLine(a.ToString());// 此不会装箱
Console.WriteLine(a.GetType());// 装箱
生成的IL指令如下
IL_0004: ldloca.s V_0
IL_0006: call instance string [mscorlib]System.Int32::ToString()
IL_000b: call void [mscorlib]System.Console::WriteLine(string)
IL_0010: nop
IL_0011: ldloc.0
IL_0012: box [mscorlib]System.Int32
IL_0017: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
IL_ 001c : call void [mscorlib]System.Console::WriteLine(object)
因为Int32重写了ToStrting方法,因此调用该方法时不会装箱,而调用GetType时则必须调用其基类Object的方法,因此必须装箱。
2. 改变已装箱的对象值
先定义一值类型的 Point
public struct Point
{
public Int32 X;
public Int32 Y;
}
再看以下操作
Point pt;
pt.X = 100;
pt.Y = 100;
Object o = pt;// 装箱
Point temp = (Point)o;// 拆箱
temp.X = 10;
temp.Y = 20;
Console.WriteLine(((Point)o).X.ToString()+"\n"+((Point)o).Y.ToString());// 两次拆箱
o = temp// 装箱
Console.WriteLine(((Point)o).X.ToString()+"\n"+((Point)o).Y.ToString());// 两次拆箱
第一次输出结果为 100 100 ,第二次输出结果为 10 20
产生的 IL 指令如下
……
IL_0013: ldloc.0
IL_0014: box Point // 第一次装箱
IL_0019: stloc.1
IL_ 001a : ldloc.1
IL_001b: unbox.any Point // 第一次拆箱
IL_0020: stloc.2
IL_0021: ldloca.s V_2
IL_0023: ldc.i4.s 10
IL_0025: stfld int32 Point::X
IL_ 002a : ldloca.s V_2
IL_ 002c : ldc.i4.s 20
IL_002e: stfld int32 Point::Y
IL_0033: ldloc.1
IL_0034: unbox.any Point // 输出时的拆箱
IL_0039: ldfld int32 Point::X
IL_003e: stloc.3
IL_ 003f : ldloca.s V_3
IL_0041: call instance string [mscorlib]System.Int32::ToString()
IL_0046: ldstr "\n"
IL_004b: ldloc.1
IL_ 004c : unbox.any Point // 输出时的拆箱
IL_0051: ldfld int32 Point::Y
IL_0056: stloc.3
IL_0057: ldloca.s V_3
IL_0059: call instance string [mscorlib]System.Int32::ToString()
IL_005e: call string [mscorlib]System.String::Concat(string,
string,
string)
IL_0063: call void [mscorlib]System.Console::WriteLine(string)
IL_0068: nop
IL_0069: ldloc.2
IL_ 006a : box Point // 第二次装箱
……
3. 通过接口改变已装箱对象的值
装箱后,由于对象不具有强类型,无法知道对象内部的结构,对象成员的改变,必须经过先拆箱,改变拆箱对象的值,最后再重新装箱这三步。以下将通过接口实现来改变已装箱对象的值。
public interface IChange
{
void Change(Int32 x,Int32 y);
}
public struct Point : IChange
{
public Int32 X;
public Int32 Y;
public void Change(Int32 x,Int32 y)
{
this.X = x;
this.Y = y;
}
}
public class MainClass
{
static void Main ()
{
Point pt;
pt.X = 100;
pt.Y = 100;
Object o = pt;
((IChange)o).Change(50,50);
Console.WriteLine(((Point)o).X);
}
}
通过接口改变已装箱的值,虽然减少了拆箱和装箱所带来的性能损失,但是需要值类型实现接口。
再来看看 C++/CLI 中的装箱与拆箱
Point pt;
pt.X = 100;
pt.Y = 100;
Point^ ptrPt = pt;// 装箱
Console::WriteLine(ptrPt->X.ToString()+"\n"+ptrPt->Y.ToString());// 这里进行了两次拆箱
ptrPt->X = 90// 拆箱
ptrPt->Y = 50;// 拆箱
Console::WriteLine(ptrPt->X.ToString()+"\n"+ptrPt->Y.ToString());
虽然 C++/CLI 对象具有强类型,但事实上,对已装箱的对象还是必须经过拆箱。只不过对 C++/CLI 程序员来说,看到的只是对对象成员的一次赋值,实际的操作都由编译器自动生成 IL 指令来完成。就上述改变坐标操作操作而言,产生的代码如下:
IL_ 005f : ldloc.0
IL_0060: unbox Point
IL_0065: ldc.i4.s 90
IL_0067: stfld int32 Point::X
IL_ 006c : ldloc.0
IL_006d: unbox Point
IL_0072: ldc.i4.s 50
IL_0074: stfld int32 Point::Y
IL_0079: ldloc.0
IL_ 007a : unbox Point
IL_ 007f : ldfld int32 Point::Y
IL_0084: stloc.3
IL_0085: ldloc.0
IL_0086: unbox Point
IL_008b: ldfld int32 Point::X
IL_0090: stloc.2
疑问: C++/CLI 中的装箱是真正的强类型么?也就是说真正了解了对象的内部构造么?如果是为何对对象的改变必须经过拆箱操作?
以上 IL 指令为何只有拆箱而没有相对应的封箱操作呢?
结论:虽然C++/CLI的装箱可以得到强类型的对象,但改变该对象还是要经过拆箱处理。