Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
252 views
in Technique[技术] by (71.8m points)

c# - How does a generic constraint prevent boxing of a value type with an implicitly implemented interface?

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...