C# 用户定义值类型的装箱
根据MSDN,如果定义了一个结构,该结构应该覆盖从对象类继承的所有方法。建议这样做,以避免在调用任何继承方法(如ToString)时出现不必要的装箱 根据MSDN,为了确定是否以及何时发生装箱,可以在MSIL代码中找到IL指令“box” 我写了下面的测试来看看拳击C# 用户定义值类型的装箱,c#,.net,il,value-type,C#,.net,Il,Value Type,根据MSDN,如果定义了一个结构,该结构应该覆盖从对象类继承的所有方法。建议这样做,以避免在调用任何继承方法(如ToString)时出现不必要的装箱 根据MSDN,为了确定是否以及何时发生装箱,可以在MSIL代码中找到IL指令“box” 我写了下面的测试来看看拳击 using System; namespace TestingBoxing { public struct StructX { public int member1; public i
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);
}
}
}
但是,在下面的MSIL代码中无法看到boxing指令,尽管在结构定义中未重写的情况下调用了ToString
.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
这怎么解释呢
参考文章:在我看来,是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#编译器可以跳过调用virtual,因为可以确定该方法不能在派生类型中重写
编辑:正如其他答案所指出的,callvirt仍然存在,是CLR进行了优化。这可以通过查看
约束的功能来解释
字段通常受约束
,以便以标准方式使用callvirt
,而无需显式地选择框。它做了以下工作:
如果thisType是引用类型(与值类型相反),那么ptr将被取消引用并作为指向callvirt of方法的“this”指针传递
如果thisType是值类型,并且thisType实现方法,那么对于thisType实现方法,ptr将作为指向调用方法指令的“this”指针进行传递,而不作修改
如果thisType是值类型,并且thisType未实现方法,则ptr将被取消引用、装箱并作为指向callvirt方法指令的“this”指针传递
这意味着(如MSDN文章所述):
只有在对象、ValueType或枚举上定义了方法且未被此类型重写时,才会出现最后一种情况。在这种情况下,装箱会导致生成原始对象的副本但是,由于Object、ValueType和Enum的任何方法都不会修改对象的状态,因此无法检测到这一事实。
我的。基本上说,如果拳击真的发生,它不能通过IL来确定
Constrained MSDN:也许它隐含在Constrained
调用中。请参见此处:.constrained在此上下文中似乎没有效果,我尝试重写ToString方法,但生成了.constrained。如果我理解正确,将始终生成constrained,只是它的作用会发生更改