值类型装箱拆箱随记

        未装箱值类型比引用类型更“轻”,这主要归功于以下两个原因:

  • 不在托管堆上分配
  • 没有堆上的每个对象的额外成员:“类型对象指针”和“同步块索引”。

        由于未装箱值类型没有同步块索引,所以不能使用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值