我的问题与此有关:Explicitly implemented interface and generic constraint

但是,我的问题是,编译器如何启用通用约束,以消除对将显式实现接口(interface)的值类型装箱的需求。

我想我的问题可以归结为两部分:

  • 幕后CLR实现是怎么回事,在访问显式实现的接口(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的内容将是什么,因此只需生成代码即可直接调用该方法。

    09-06 20:05