泛型约束如何防止对具有隐式实现接口的值类型进行装箱? [英] How does a generic constraint prevent boxing of a value type with an implicitly implemented interface?

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

问题描述

我的问题与这个有点相关:显式实现的接口和通用约束.

然而,我的问题是编译器如何启用通用约束来消除对显式实现接口的值类型进行装箱的需要.

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

  1. 在访问显式实现的接口成员时需要对值类型进行装箱的幕后 CLR 实现发生了什么,以及

  2. 删除此要求的通用约束会发生什么?

一些示例代码:

内部结构TestStruct:IEquatable{bool IEquatable.Equals(TestStruct other){返回真;}}内部类 TesterClass{//方法public static bool AreEqual(T arg1, T arg2) 其中T:IEquatable{返回 arg1.Equals(arg2);}公共静态无效运行(){TestStruct t1 = new TestStruct();TestStruct t2 = new TestStruct();Debug.Assert(((IEquatable) t1).Equals(t2));Debug.Assert(AreEqual(t1, t2));}}

以及由此产生的 IL:

.class 私有顺序 ansi 密封 beforefieldinit TestStruct扩展 [mscorlib]System.ValueType实现 [mscorlib]System.IEquatable`1{.method private hidebysig newslot virtual final instance bool System.IEquatable.Equals(valuetype TestStruct other) cil managed{.override [mscorlib]System.IEquatable`1::Equals.maxstack 1.locals 初始化([0] bool CS$1$0000)L_0000:没有L_0001: ldc.i4.1L_0002:stloc.0L_0003: br.s L_0005L_0005: ldloc.0L_0006:返回}}.class private auto ansi beforefieldinit TesterClass扩展 [mscorlib]System.Object{.method public hidebysig specialname rtspecialname instance void .ctor() cil managed{.maxstack 8L_0000:ldarg.0L_0001:调用实例 void [mscorlib]System.Object::.ctor()L_0006:返回}.method public hidebysig static bool AreEqual<([mscorlib]System.IEquatable`1) T>(!!T arg1, !!T arg2) cil managed{.maxstack 2.locals 初始化([0] bool CS$1$0000)L_0000:没有L_0001: ldarga.s arg1L_0003:ldarg.1L_0004:受限!!TL_000a: callvirt instance bool [mscorlib]System.IEquatable`1::Equals(!0)L_000f:stloc.0L_0010: br.s L_0012L_0012: ldloc.0L_0013:返回}.method public hidebysig static void Run() cil 管理{.maxstack 2.locals 初始化([0] 值类型 TestStruct t1,[1] 值类型 TestStruct t2,[2] bool areEqual)L_0000:没有L_0001: ldloca.s t1L_0003: initobj TestStructL_0009: ldloca.s t2L_000b: initobj TestStructL_0011: ldloc.0L_0012:框 TestStructL_0017: ldloc.1L_0018: callvirt instance bool [mscorlib]System.IEquatable`1::Equals(!0)L_001d:stloc.2L_001e:ldloc.2L_001f:调用 void [System]System.Diagnostics.Debug::Assert(bool)L_0024:没有L_0025: ldloc.0L_0026: ldloc.1L_0027:调用 bool TesterClass::AreEqual(!!0, !!0)L_002c:stloc.2L_002d:ldloc.2L_002e:调用 void [System]System.Diagnostics.Debug::Assert(bool)L_0033:没有L_0034:返回}}

关键调用是constrained !!T 而不是box TestStruct,但是后续的调用在两种情况下仍然是callvirt.>

所以我不知道进行虚拟调用所需的装箱是什么,我特别不明白如何使用限制到值类型的泛型消除对装箱操作的需要.

我先谢谢大家...

然而,我的问题是编译器如何启用通用约束来消除对显式实现接口的值类型进行装箱的需要.

编译器"不清楚您指的是抖动还是 C# 编译器.C# 编译器通过在虚拟调用上发出受约束的前缀来实现这一点.请参阅约束前缀的文档详情.

<块引用>

在访问显式实现的接口成员时需要将值类型装箱的幕后 CLR 实现是怎么回事

被调用的方法是否是显式实现的接口成员并不是特别重要.一个更普遍的问题是为什么任何虚拟调用都需要对值类型进行装箱?

传统上认为虚调用是对虚函数表中方法指针的间接调用.这并不是接口调用在 CLR 中的确切工作方式,但对于本次讨论而言,它是一个合理的思维模型.

如果这是调用虚方法的方式,那么虚表从何而来?值类型中没有 vtable.值类型仅在其存储中具有其值.装箱创建对对象的引用,该对象具有设置为指向所有值类型的虚拟方法的虚表.(再次提醒您,这不是完全接口调用的工作方式,但这是一个很好的思考方式.)

<块引用>

删除此要求的通用约束会发生什么?

抖动将为泛型方法的每个不同值类型参数构造生成代码.如果您要为每种不同的值类型生成新代码,那么您可以针对该特定值类型定制该代码.这意味着你不必建立一个vtable然后查找vtable的内容是什么!知道vtable的内容是什么,直接生成代码调用方法即可.

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...

解决方案

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.

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.

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

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.)

What happens with a generic constraint that removes this requirement?

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.

这篇关于泛型约束如何防止对具有隐式实现接口的值类型进行装箱?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆