用户定义值类型的装箱

Boxing of user-defined value types

根据 MSDN,如果定义了结构,则该结构应覆盖从对象 class 继承的所有方法。建议在调用任何继承方法(如 ToString)时避免不必要的装箱。

根据MSDN,判断是否以及何时发生装箱,可以在MSIL代码中找到IL指令"box"。

我写了下面的测试看看装箱。

using System;

namespace TestingBoxing
{
    public struct StructX
    {
        public int member1;
        public int member2;
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            StructX s1;

            s1.member1 = 2;
            s1.member2 = 5;

            string str = s1.ToString();

            Console.WriteLine(str);
        }
    }
}

然而,尽管在结构定义中调用了 ToString 而没有被覆盖,但在下面的 MSIL 代码中看不到装箱指令。

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       37 (0x25)
  .maxstack  2
  .locals init ([0] valuetype TestingBoxing.StructX s1,
           [1] string str)
  IL_0000:  ldloca.s   s1
  IL_0002:  ldc.i4.2
  IL_0003:  stfld      int32 TestingBoxing.StructX::member1
  IL_0008:  ldloca.s   s1
  IL_000a:  ldc.i4.5
  IL_000b:  stfld      int32 TestingBoxing.StructX::member2
  IL_0010:  ldloca.s   s1
  IL_0012:  constrained. TestingBoxing.StructX
  IL_0018:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_001d:  stloc.1
  IL_001e:  ldloc.1
  IL_001f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0024:  ret
} // end of method Program::Main 

如何解释?

参考文章:http://msdn.microsoft.com/en-us/library/ms973858.aspx#code-snippet-6

在我看来,装箱的是 callvirt 指令。查看代码的反汇编,我们在调用 ToString

的行上得到了这个反汇编
00DB287A  mov         ecx,26933C0h  
00DB287F  call        00AD2100  
00DB2884  mov         dword ptr [ebp-18h],eax  
00DB2887  mov         edi,dword ptr [ebp-18h]  
00DB288A  add         edi,4  
00DB288D  lea         esi,[ebp-10h]  
00DB2890  movq        xmm0,mmword ptr [esi]  
00DB2894  movq        mmword ptr [edi],xmm0  
00DB2898  mov         ecx,dword ptr [ebp-18h]  
00DB289B  mov         eax,dword ptr [ecx]  
00DB289D  mov         eax,dword ptr [eax+28h]  
00DB28A0  call        dword ptr [eax]  
00DB28A2  mov         dword ptr [ebp-1Ch],eax  
00DB28A5  mov         eax,dword ptr [ebp-1Ch]  
00DB28A8  mov         dword ptr [ebp-14h],eax 

如果我们将代码更改为:

public struct StructX
{
    public int member1;
    public int member2;

    public override string ToString()
    {
        return member1.ToString() + " " + member2.ToString();
    }
}

我们得到:

02352875  lea         ecx,[ebp-8]  
02352878  call        dword ptr ds:[4DD33E0h]  
0235287E  mov         dword ptr [ebp-10h],eax  
02352881  mov         eax,dword ptr [ebp-10h]  
02352884  mov         dword ptr [ebp-0Ch],eax  

现在我的组件很生疏了,但在我看来,所有这些移动实际上都是拳击。当类型是值类型时 C# 编译器可以跳过 call virtual 因为可以确定该方法不能在派生类型中被覆盖。

编辑:正如其他答案所指出的那样,callvirt 仍然存在,它是由 CLR 进行优化的。

这可以通过查看 Constrained 的作用来解释。

字段通常是 ​​constrained 以便以标准方式使用 callvirt 而无需显式装箱。它执行以下操作:

If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.

If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.

If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

这意味着什么(如 MSDN 文章所述):

This last case can occur only when method was defined on Object, ValueType, or Enum and not overridden by thisType. In this case, the boxing causes a copy of the original object to be made. However, because none of the methods of Object, ValueType, and Enum modify the state of the object, this fact cannot be detected.

强调我的。基本上就是说,如果确实发生了装箱,是无法通过IL来判断的。

受约束的 MSDN:http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained%28v=vs.110%29.aspx