本文介绍了如何与一个隐式实现的接口值类型的泛型约束prevent拳?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题是有些涉及到这一块:Explicitly实现接口和泛型约束。

我的问题,但是,是的如何的编译器实现了通用的限制,无需拳击明确地实现接口值类型。

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

  1. 什么是与背后的幕后CLR实现,需要一个值类型的访问显式实现接口成员,以及何时进行装箱回事

  2. 与移除此需求的通用的限制,会发生什么?

一些例如code:

 内部结构TestStruct:IEquatable< TestStruct>
{
    布尔IEquatable< TestStruct> .Equals(TestStruct等)
    {
        返回true;
    }
}

内部类TesterClass
{
    //方法
    公共静态布尔AreEqual< T>(T ARG1,T ARG2)其中T:IEquatable< T>
    {
        返回arg1.Equals(ARG2);
    }

    公共静态无效的run()
    {
        TestStruct T1 =新TestStruct();
        TestStruct T2 =新TestStruct();
        Debug.Assert的(((IEquatable&其中; TestStruct&GT)T1).Equals(T2));
        Debug.Assert的(AreEqual&其中; TestStruct>(T1,T2));
    }
}
 

和所产生的IL:

 的.class私人连续ANSI密封beforefieldinit TestStruct
    扩展[mscorlib程序] System.ValueType
    农具[mscorlib程序] System.IEquatable`1<值类型TestStruct>
{
    。方法私人hidebysig newslot布尔System.IEquatable&LT虚拟终审; TestStruct> .Equals(值类型TestStruct等)CIL管理
    {
        .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私人汽车ANSI beforefieldinit TesterClass
    扩展[mscorlib程序] System.Object的
{
    。方法公开hidebysig specialname rtspecialname实例无效.ctor()CIL管理
    {
        .maxstack 8
        L_0000:ldarg.0
        L_0001:呼叫实例无效[mscorlib程序] System.Object的构造函数::()。
        L_0006:RET
    }

    。方法公开hidebysig静态布尔AreEqual≤([mscorlib程序] System.IEquatable`1<!T>)T>(!!牛逼ARG1,!!牛逼ARG2)CIL管理
    {
        .maxstack 2
        .locals的init(
            [0] BOOL CS $ 1 $ 0000)
        L_0000:NOP
        L_0001:ldarga.s ARG1
        L_0003:ldarg.1
        L_0004:约束!牛逼
        L_000a:callvirt例如布尔[mscorlib程序] System.IEquatable`1<!T> ::等于(!0)
        L_000f:stloc.0
        L_0010:br.s L_0012
        L_0012:ldloc.0
        L_0013:RET
    }

    。方法公开hidebysig静态无效的run()CIL管理
    {
        .maxstack 2
        .locals的init(
            [0]值类型TestStruct t1时,
            [1]值类型TestStruct t2时,
            [2]布尔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:箱TestStruct
        L_0017:ldloc.1
        L_0018:callvirt例如布尔[mscorlib程序] System.IEquatable`1<值类型TestStruct> ::等于(!0)
        L_001d:stloc.2
        L_001e:ldloc.2
        L_001f:拨打无效[系统] System.Diagnostics.Debug ::断言(布尔)
        L_0024:NOP
        L_0025:ldloc.0
        L_0026:ldloc.1
        L_0027:叫布尔TesterClass :: AreEqual<值类型TestStruct>(!! 0!0)
        L_002c:stloc.2
        L_002d:ldloc.2
        L_002e:拨打无效[系统] System.Diagnostics.Debug ::断言(布尔)
        L_0033:NOP
        L_0034:RET
    }

}
 

键呼叫约束!!牛逼而不是中TestStruct ,但是随后的电话仍是 callvirt 在这两种情况下。

所以我不知道它是与被要求建立一个虚拟呼叫拳什么的,我特别不明白如何使用一个通用的约束为值类型,则不再需要的装箱操作。

我感谢大家提前...

解决方案

通过编译目前尚不清楚是否意味着抖动或者C#编译器。 C#编译器通过发射受限preFIX上的虚拟呼叫这样做。见的约束preFIX的文档的详细信息。

<一个href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.op$c$cs.constrained.aspx">http://msdn.microsoft.com/en-us/library/system.reflection.emit.op$c$cs.constrained.aspx

无论是被调用的方法是一个显式实现接口成员与否并不特别重要。一个更普遍的问题是,为什么做的任意的虚拟呼叫所需的值类型进行装箱?

一个传统认为虚拟呼叫作为一个方法指针的虚函数表的间接调用。这并不是完全的接口调用的CLR是如何工作的,但它是本次讨论的目的,合理的心智模型。

如果这是一个虚拟的方法是怎么样被调用然后的哪里虚函数表来自的?值类型没有在这一个虚函数表。值类型只是有它在其存储价值。拳击创建一个引用一个对象,它有一个虚函数表设置为指向所有的值类型的虚拟方法。 (我再次提醒你,这是没有的完全的接口调用是如何工作的,但想想它的好方法。)

抖动将要产生的新鲜 code的通用方法的每个不同的值类型参数构造。如果你将要产生新的code为每个不同的值类型,那么你可以定制的code到特定的值类型。这意味着你不必建立一个虚函数表,然后查找什么虚函数表的内容!你知道什么是虚函数表的内容将是,所以才产生了code直接调用该方法。

My question is somewhat related to this one: Explicitly implemented interface and generic constraint.

My question, however, is how the compiler enables a generic constraint to eliminate the need for boxing a value type that explicitly implements an interface.

I guess my question boils down to two parts:

  1. What is going on with the behind-the-scenes CLR implementation that requires a value type to be boxed when accessing an explicitly implemented interface member, and

  2. What happens with a generic constraint that removes this requirement?

Some example code:

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));
    }
}

And the resultant 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
    }

}

The key call is constrained !!T instead of box TestStruct, but the subsequent call is still callvirt in both cases.

So I don't know what it is with boxing that is required to make a virtual call, and I especially do not understand how using a generic constrained to a value type removes the need for the boxing operation.

I thank everyone in advance...

解决方案

By "the compiler" it is not clear whether you mean the jitter or the C# compiler. The C# compiler does so by emitting the constrained prefix on the virtual call. See the documentation of the constrained prefix for details.

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained.aspx

Whether the method being invoked is an explicitly implemented interface member or not is not particularly relevant. A more general question would be why does any virtual call require the value type to be boxed?

One traditionally thinks of a virtual call as being an indirect invocation of a method pointer in a virtual function table. That's not exactly how interface invocations work in the CLR, but it's a reasonable mental model for the purposes of this discussion.

If that's how a virtual method is going to be invoked then where does the vtable come from? The value type doesn't have a vtable in it. The value type just has its value in its storage. Boxing creates a reference to an object that has a vtable set up to point to all the value type's virtual methods. (Again, I caution you that this is not exactly how interface invocations work, but it is a good way to think about it.)

The jitter is going to be generating fresh code for each different value type argument construction of the generic method. If you're going to be generating fresh code for each different value type then you can tailor that code to that specific value type. Which means that you don't have to build a vtable and then look up what the contents of the vtable are! You know what the contents of the vtable are going to be, so just generate the code to invoke the method directly.

这篇关于如何与一个隐式实现的接口值类型的泛型约束prevent拳?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

06-27 18:40