装箱用户定义的值类型

时间:2015-01-18 10:31:00

标签: c# .net il value-type

根据MSDN,如果定义了一个结构,那么该结构应该覆盖从该对象类继承的所有方法。建议在调用任何继承的方法(如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

2 个答案:

答案 0 :(得分:6)

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

字段通常为constrained,以便以标准方式使用callvirt,而无需明确选中。它执行以下操作:

  

如果thisType是引用类型(而不是值类型),则取消引用ptr并将其作为'this'指针传递给方法的callvirt。

     

如果thisType是一个值类型而thisType实现了方法,那么ptr将被未经修改地传递为调用方法指令的'this'指针,用于通过thisType实现方法。

     

如果thisType是一个值类型而thisType没有实现方法,则ptr被解除引用,装箱,并作为'this'指针传递给callvirt方法指令。

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

  

最后一种情况只有在Object,ValueType或Enum上定义方法并且不被thisType覆盖时才会发生。在这种情况下,装箱会导致原始对象的副本。 但是,由于Object,ValueType和Enum的方法都没有修改对象的状态,因此无法检测到这一事实。

强调我的。基本上说如果拳击确实发生,则无法通过IL确定。

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

答案 1 :(得分:1)

在我看来,这是拳击的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#编译器可以跳过调用虚拟,因为可以确定该方法无法在派生类型中被覆盖。

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