根据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
答案 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确定。
答案 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。