ildasm测试的debug版本,与release版本情况可能不符
《CLR via C#》中文第4版P112写道
“拆箱其实就是获取指针的过程,该指针包含在一个对象的原始值类型(数据字段)。其实,指针指向的是已装箱失利中的未装箱部分。所以和装箱不同,拆箱不要求在内存中赋值任何字节”
我一开始的理解是,对于一个普通的表达式Point a = (Point) o, (Point)o 强制类型转换这部分是拆箱,然后赋值运算符导致了赋值,所以拆箱不要求在内存中赋值任何字节,赋值是赋值运算符导致的。
但是在P121又展示了一个例子。
using System;
internal struct Point
{
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
}
public void Change(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
}
public override string ToString()
{
return String.Format("({0} {1})", m_x.ToString(), m_y.ToString());
}
}
class Program
{
public static void Main(string[] args)
{
Point p = new Point(1, 1);
Console.WriteLine(p);
p.Change(2, 2);
Console.WriteLine(p);
Object o = p;
Console.WriteLine(o);
((Point)o).Change(3,3);
Console.WriteLine(o);
}
}
书中也说道, ((Point)o).Change(3,3) 会产生一个临时的Point,临时的Point 的值被赋为(3,3)。
在这个例子中,没有使用赋值运算符,也产生了一个临时的变量,和P112讲的不一致。
于是使用ildasm查看了一下。
对IL语言不太了解可以看下译文MSIL简介
.method public hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 83 (0x53)
.maxstack 3
.locals init ([0] valuetype Program.Point p,
[1] object o,
[2] valuetype Program.Point V_2)
IL_0000: nop
IL_0001: ldloca.s p
IL_0003: ldc.i4.1
IL_0004: ldc.i4.1
IL_0005: call instance void Program.Point::.ctor(int32,
int32)
IL_000a: ldloc.0
IL_000b: box Program.Point
IL_0010: call void [mscorlib]System.Console::WriteLine(object)
IL_0015: nop
IL_0016: ldloca.s p
IL_0018: ldc.i4.2
IL_0019: ldc.i4.2
IL_001a: call instance void Program.Point::Change(int32,
int32)
IL_001f: nop
IL_0020: ldloc.0
IL_0021: box Program.Point
IL_0026: call void [mscorlib]System.Console::WriteLine(object)
IL_002b: nop
IL_002c: ldloc.0
IL_002d: box Program.Point
IL_0032: stloc.1
IL_0033: ldloc.1
IL_0034: call void [mscorlib]System.Console::WriteLine(object)
IL_0039: nop
IL_003a: ldloc.1
IL_003b: unbox.any Program.Point
IL_0040: stloc.2
IL_0041: ldloca.s V_2
IL_0043: ldc.i4.3
IL_0044: ldc.i4.3
IL_0045: call instance void Program.Point::Change(int32,
int32)
IL_004a: nop
IL_004b: ldloc.1
IL_004c: call void [mscorlib]System.Console::WriteLine(object)
IL_0051: nop
IL_0052: ret
} // end of method Program::Main
其中
IL_003a: ldloc.1
IL_003b: unbox.any Program.Point
IL_0040: stloc.2
IL_0041: ldloca.s V_2
IL_0043: ldc.i4.3
IL_0044: ldc.i4.3
IL_0045: call instance void Program.Point::Change(int32,int32)
对应代码 ((Point)o).Change(3,3)
我将代码 ((Point)o).Change(3,3) 修改为 Point a = (Point)o; a.Change(3,3)之后,IL代码几乎没有变化,只有局部变量的名称由V_2变成了a。
再看 unbox.any 这个指令。
文档的解释是 1.object对象引用被推入栈中 2.object引用从栈中弹出,拆箱成指令指定的类型 3.返回的object引用或者值类型被推入栈中
也就是说执行了unbox.any必定会导致复制一个值类型到栈中,紧接着stloc.2将栈中的值类型赋值到第2个局部变量中,也就是V_2。
这里发生了两次复制,一次是从堆中的对象复制信息到栈中,再从栈中复制信息到局部变量。
我又试了一下将代码 ((Point)o).Change(3,3) 修改为 ((Point)o) 观察结果是不是只有一次从堆中的对象复制信息到栈中。然后编译器报错…..也就是说,拆箱必定导致两次复制,而不是书上说的不要求复制任何字节。