我的问题与此有关:Explicitly implemented interface and generic constraint。
但是,我的问题是,编译器如何启用通用约束,以消除对将显式实现接口(interface)的值类型装箱的需求。
我想我的问题可以归结为两部分:
一些示例代码:
internal struct TestStruct : IEquatable<TestStruct>
{
bool IEquatable<TestStruct>.Equals(TestStruct other)
{
return true;
}
}
internal class TesterClass
{
// Methods
public static bool AreEqual<T>(T arg1, T arg2) where T: IEquatable<T>
{
return arg1.Equals(arg2);
}
public static void Run()
{
TestStruct t1 = new TestStruct();
TestStruct t2 = new TestStruct();
Debug.Assert(((IEquatable<TestStruct>) t1).Equals(t2));
Debug.Assert(AreEqual<TestStruct>(t1, t2));
}
}
以及最终的IL:
.class private sequential ansi sealed beforefieldinit TestStruct
extends [mscorlib]System.ValueType
implements [mscorlib]System.IEquatable`1<valuetype TestStruct>
{
.method private hidebysig newslot virtual final instance bool System.IEquatable<TestStruct>.Equals(valuetype TestStruct other) cil managed
{
.override [mscorlib]System.IEquatable`1<valuetype TestStruct>::Equals
.maxstack 1
.locals init (
[0] bool CS$1$0000)
L_0000: nop
L_0001: ldc.i4.1
L_0002: stloc.0
L_0003: br.s L_0005
L_0005: ldloc.0
L_0006: ret
}
}
.class private auto ansi beforefieldinit TesterClass
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.method public hidebysig static bool AreEqual<([mscorlib]System.IEquatable`1<!!T>) T>(!!T arg1, !!T arg2) cil managed
{
.maxstack 2
.locals init (
[0] bool CS$1$0000)
L_0000: nop
L_0001: ldarga.s arg1
L_0003: ldarg.1
L_0004: constrained !!T
L_000a: callvirt instance bool [mscorlib]System.IEquatable`1<!!T>::Equals(!0)
L_000f: stloc.0
L_0010: br.s L_0012
L_0012: ldloc.0
L_0013: ret
}
.method public hidebysig static void Run() cil managed
{
.maxstack 2
.locals init (
[0] valuetype TestStruct t1,
[1] valuetype TestStruct t2,
[2] bool areEqual)
L_0000: nop
L_0001: ldloca.s t1
L_0003: initobj TestStruct
L_0009: ldloca.s t2
L_000b: initobj TestStruct
L_0011: ldloc.0
L_0012: box TestStruct
L_0017: ldloc.1
L_0018: callvirt instance bool [mscorlib]System.IEquatable`1<valuetype TestStruct>::Equals(!0)
L_001d: stloc.2
L_001e: ldloc.2
L_001f: call void [System]System.Diagnostics.Debug::Assert(bool)
L_0024: nop
L_0025: ldloc.0
L_0026: ldloc.1
L_0027: call bool TesterClass::AreEqual<valuetype TestStruct>(!!0, !!0)
L_002c: stloc.2
L_002d: ldloc.2
L_002e: call void [System]System.Diagnostics.Debug::Assert(bool)
L_0033: nop
L_0034: ret
}
}
关键调用是
constrained !!T
而不是box TestStruct
,但是在两种情况下,后续调用仍然是callvirt
。因此,我不知道进行虚拟调用所需的装箱是什么,而且我尤其不了解如何使用受约束于值类型的泛型来消除装箱操作。
我预先感谢大家...
最佳答案
用“编译器”不清楚是抖动还是C#编译器。 C#编译器通过在虚拟调用中发出受约束的前缀来做到这一点。有关详细信息,请参见the documentation of the constrained prefix。
被调用的方法是否是显式实现的接口(interface)成员不是特别重要。一个更笼统的问题是,为什么任何虚拟调用都要求将值类型装箱?
传统上将虚拟调用视为虚拟函数表中方法指针的间接调用。这不是CLR中接口(interface)调用的工作方式,但这是本讨论目的的合理思维模型。
如果那是将要调用虚拟方法的方式,那么vtable的来源是什么?值类型中没有vtable。值类型仅在其存储中具有其值。装箱创建对对象的引用,该对象的vtable设置为指向所有值类型的虚拟方法。 (再次,我告诫您,这并不是接口(interface)调用的确切工作方式,但这是考虑它的好方法。)
抖动将为泛型方法的每个不同值类型参数构造生成新的代码。如果要为每种不同的值类型生成新的代码,则可以将该代码定制为该特定的值类型。这意味着您不必构建vtable,然后查找vtable的内容!您知道vtable的内容将是什么,因此只需生成代码即可直接调用该方法。