用户定义值类型的装箱
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,如果定义了结构,则该结构应覆盖从对象 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来判断的。