Why does generic method with constraint of T: class result in boxing? [duplicate]

本小妞迷上赌 提交于 2019-12-04 00:30:21

You don't have to worry about any performance-degradations from the box instruction because if its argument is a reference type, the box instruction does nothing. Though it's still strange that the box instruction has even been created (maybe lazyiness/easier design at code generation?).

I'm not sure why any boxing is ocurring. One possible way to avoid the boxing is to not use it. Just recompile without the boxing. Ex:

.assembly recomp_srp
{
    .ver 1:0:0:0
}

.class public auto ansi FixedPBF
{

.method public instance void .ctor() cil managed
{

}

.method hidebysig public instance void SetRefProperty<class T>(!!T& propertyBackingField, !!T newValue) cil managed
{
    .maxstack 2    
        .locals init ( bool isDifferent, bool CS$4$0000)

        ldc.i4.0
        stloc.0
        ldarg.1
        ldobj !!T
        ldarg.2
        ceq
        stloc.1
        ldloc.1
        brtrue.s L_0001
        ldc.i4.1
        stloc.0
        L_0001: ret

}

}

...if you save to a file recomp_srp.msil you can simply recompile as such:

ildasm /dll recomp_srp.msil

And it runs OK without the boxing on my end:

        FixedPBF TestFixedPBF = new FixedPBF();

        TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");

...of course, I changed it from protected to public, you would need to make the change back again and provide the rest of your implementation.

I believe this is intended by design. You're not constraining T to a specific class so it's most likely down casting it to object. Hence why you see the IL include boxing.

I would try this code with where T : ActualClass

Following up on a couple points. First of all, this bug occurs for both methods in a generic class with constraint where T : class and also generic methods with that same constraint (in a generic or non-generic class). It does not occur for an (otherwise identical) non-generic method which uses Object instead of T:

// static T XchgNullCur<T>(ref T addr, T value) where T : class =>
//              Interlocked.CompareExchange(ref addr, val, null) ?? value;
    .locals init (!T tmp)
    ldarg addr
    ldarg val
    ldloca tmp
    initobj !T
    ldloc tmp
    call !!0 Interlocked::CompareExchange<!T>(!!0&, !!0, !!0)
    dup 
    box !T
    brtrue L_001a
    pop 
    ldarg val
L_001a:
    ret 


// static Object XchgNullCur(ref Object addr, Object val) =>
//                   Interlocked.CompareExchange(ref addr, val, null) ?? value;
    ldarg addr
    ldarg val
    ldnull
    call object Interlocked::CompareExchange(object&, object, object)
    dup
    brtrue L_000d
    pop
    ldarg val
L_000d:
    ret

Notice some additional problems with the first example. Instead of simply ldnull we have an extraneous initobj call pointlessly targeting an excess local variable tmp.

The good news however, hinted-at here, is that none of this matters. Despite the differences in the IL code generated for the two examples above, the x64 JIT generates nearly identical code for them. The following result is for .NET Framework 4.7.2 release mode with optimization "not-suppressed".

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!