未装箱值类型比引用类型更“轻”,这主要归功于以下两个原因:
- 不在托管堆上分配
- 没有堆上的每个对象的额外成员:“类型对象指针”和“同步块索引”。
由于未装箱值类型没有同步块索引,所以不能使用System.Threding.Monitor类型的方法(或者C# lock语句)让多个线程同步对实例的访问。
虽然未装箱值类型没有类型对象指针,但仍可以调用由类型继承或重写的虚方法(比如Equals,GetHashCode或者ToString)。如果值类型重写了其中任何虚方法,那么CLR可以非虚地调用该方法,因为值类型隐式密封,不可能有类型从它们派生,而且调用虚方法的值类型实例没有装箱。然而,如果重写的虚方法要调用方法在基类的实现时,值类型实例会装箱,以便能够通过this指针将对一个堆对象的引用传给基方法。
但在调用非虚的、继承的方法时(比如GetType或MemberwiseClone),无论如何都要对值类型进行装箱。因为这些方法由System.Object定义,要求this实参是指向堆对象的指针。
此外,将值类型的未装箱实例转型为类型的某个接口时要对实例进行装箱。这是因为接口变量必须包含对堆对象的引用(接口主题将在13章“接口”中讨论)。以下代码对此进行了演示。
代码中使用的是顶级语言格式。
#region 值类型的常见装箱拆箱操作
//在栈上创建两个Point实例
Point p1 = new Point(10, 10);
Point p2 = new Point(20, 20);
//调用ToString(虚方法)不装箱p1
Console.WriteLine(p1.ToString());//显示“(10,10)”
//调用GetType(非虚方法)时,要对p1进行装箱
Console.WriteLine(p1.GetType());//显示“Point”
//调用CompareTo(虚方法)不装箱p1
//由于调用的是CompareTo(Point),所以p2不装箱
Console.WriteLine(p1.CompareTo(p2));//显示“-1”
//p1要装箱,引用放到c中
IComparable c = p1;
Console.WriteLine(c.GetType());//显示“Point”
//调用CompareTo不装箱p1
//由于CompareTo传递的不是Point变量,所以调用的是CompareTo(object),它要求获取已装箱Point的引用
//c不装箱是因为它本来就引用已装箱Point
Console.WriteLine(p1.CompareTo(c));//显示“0”
//c不装箱,因为它本来就引用已装箱Point
//p2要装箱,因为调用的是CompareTo(Point)
Console.WriteLine(c.CompareTo(p2));//显示“-1”
//对c拆箱,字段复制到p2中
p2 = (Point)c;
//证明字段已复制到p2中
Console.WriteLine(p2.ToString());//显示“(10,10)”
internal struct Point : IComparable
{
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
}
//重写从System.ValueType继承的ToString方法
public override string ToString()
{
//将point作为字符串返回。注意,调用ToStirng以避免装箱
return String.Format("({0},{1})", m_x.ToString(), m_y.ToString());
}
//实现类型安全的CompareTo方法
public Int32 CompareTo(Point other)
{
//利用勾股定理计算哪个point就离原点(0,0)更远
return Math.Sign(Math.Sqrt(m_x*m_x + m_y*m_y) - Math.Sqrt(other.m_x*other.m_x + other.m_y*other.m_y));
}
//重写从IComparable继承的CompareTo方法
public int CompareTo(object? o)
{
if (GetType() != o.GetType())
{
throw new ArgumentException("o is not a Point");
}
//调用类型安全的CompareTo
return CompareTo((Point)o);
}
}
#endregion
IL代码对比,查看装箱拆箱验证。
// Type: Program
// Assembly: TestRefAndVal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 6FB5EDE4-0121-4838-968D-4DB5D883A8FB
// Location: F:\RiderProjects\Demo5-2\TestRefAndVal\bin\Debug\net8.0\TestRefAndVal.dll
// Sequence point data and variable names from f:\riderprojects\demo5-2\testrefandval\bin\debug\net8.0\testrefandval.pdb
.class private auto ansi beforefieldinit
Program
extends [System.Runtime]System.Object
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.method private hidebysig static void
'<Main>$'(
string[] args
) cil managed
{
.entrypoint
.maxstack 3
.locals init (
[0] valuetype Point p1,
[1] valuetype Point p2,
[2] class [System.Runtime]System.IComparable c
)
// [49 1 - 49 30]
IL_0000: ldloca.s p1
IL_0002: ldc.i4.s 10 // 0x0a
IL_0004: ldc.i4.s 10 // 0x0a
IL_0006: call instance void Point::.ctor(int32, int32)
// [50 1 - 50 30]
IL_000b: ldloca.s p2
IL_000d: ldc.i4.s 20 // 0x14
IL_000f: ldc.i4.s 20 // 0x14
IL_0011: call instance void Point::.ctor(int32, int32)
// [53 1 - 53 34]
IL_0016: ldloca.s p1
IL_0018: constrained. Point
IL_001e: callvirt instance string [System.Runtime]System.Object::ToString()
IL_0023: call void [System.Console]System.Console::WriteLine(string)
IL_0028: nop
// [56 1 - 56 33]
IL_0029: ldloc.0 // p1
IL_002a: box Point
IL_002f: call instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType()
IL_0034: call void [System.Console]System.Console::WriteLine(object)
IL_0039: nop
// [60 1 - 60 37]
IL_003a: ldloca.s p1
IL_003c: ldloc.1 // p2
IL_003d: call instance int32 Point::CompareTo(valuetype Point)
IL_0042: call void [System.Console]System.Console::WriteLine(int32)
IL_0047: nop
// [63 1 - 63 20]
IL_0048: ldloc.0 // p1
IL_0049: box Point
IL_004e: stloc.2 // c
// [64 1 - 64 32]
IL_004f: ldloc.2 // c
IL_0050: callvirt instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType()
IL_0055: call void [System.Console]System.Console::WriteLine(object)
IL_005a: nop
// [69 1 - 69 36]
IL_005b: ldloca.s p1
IL_005d: ldloc.2 // c
IL_005e: call instance int32 Point::CompareTo(object)
IL_0063: call void [System.Console]System.Console::WriteLine(int32)
IL_0068: nop
// [73 1 - 73 36]
IL_0069: ldloc.2 // c
IL_006a: ldloc.1 // p2
IL_006b: box Point
IL_0070: callvirt instance int32 [System.Runtime]System.IComparable::CompareTo(object)
IL_0075: call void [System.Console]System.Console::WriteLine(int32)
IL_007a: nop
// [76 1 - 76 15]
IL_007b: ldloc.2 // c
IL_007c: unbox.any Point
IL_0081: stloc.1 // p2
// [78 1 - 78 34]
IL_0082: ldloca.s p2
IL_0084: constrained. Point
IL_008a: callvirt instance string [System.Runtime]System.Object::ToString()
IL_008f: call void [System.Console]System.Console::WriteLine(string)
IL_0094: nop
IL_0095: ret
} // end of method Program::'<Main>$'
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
} // end of class Program