为什么带有T:class约束的泛型方法导致装箱? [重复]

问题描述 投票:12回答:4

这个问题在这里已有答案:

为什么将T限制为类的泛型方法会在生成MSIL代码中有装箱指令?

我对此感到非常惊讶,因为由于T被约束为引用类型,因此生成的代码不需要执行任何装箱。

这是c#代码:

protected void SetRefProperty<T>(ref T propertyBackingField, T newValue) where T : class
{
    bool isDifferent = false;

    // for reference types, we use a simple reference equality check to determine
    // whether the values are 'equal'.  We do not use an equality comparer as these are often
    // unreliable indicators of equality, AND because value equivalence does NOT indicate
    // that we should share a reference type since it may be a mutable.

    if (propertyBackingField != newValue)
    {
        isDifferent = true;
    }
}

这是生成的IL:

.method family hidebysig instance void SetRefProperty<class T>(!!T& propertyBackingField, !!T newValue) cil managed
{
    .maxstack 2
    .locals init (
        [0] bool isDifferent,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: ldarg.1 
    L_0004: ldobj !!T
    L_0009: box !!T
    L_000e: ldarg.2 
    L_000f: box !!T
    L_0014: ceq 
    L_0016: stloc.1 
    L_0017: ldloc.1 
    L_0018: brtrue.s L_001e
    L_001a: nop 
    L_001b: ldc.i4.1 
    L_001c: stloc.0 
    L_001d: nop 
    L_001e: ret 
}

注意方框!! T说明。

为什么要生成这个?

怎么避免这个?

c# .net generics boxing
4个回答
2
投票

您不必担心box指令的任何性能下降,因为如果它的参数是引用类型,则box指令不执行任何操作。尽管奇怪的是box指令甚至已经被创建(在代码生成时可能是懒惰/更容易设计?)。


1
投票

我不确定为什么会发生拳击。避免拳击的一种可能方法是不使用它。只需重新编译没有拳击。例如:

.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

}

}

...如果您保存到文件recomp_srp.msil,您可以简单地重新编译:

ildasm / etc recomp_srp.msil

它运行正常没有我的拳击:

        FixedPBF TestFixedPBF = new FixedPBF();

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

...当然,我将它从受保护更改为公共,您需要再次进行更改并提供其余的实现。


0
投票

我相信这是设计意图。你没有将T限制在一个特定的类中,因此很可能将它强制转换为对象。因此,为什么你看到IL包括拳击。

我会尝试使用TO:Actual Class的代码


0
投票

跟进几点。首先,对于具有约束where T : class的泛型类中的两个方法以及具有相同约束的泛型方法(在泛型或非泛型类中),会发生此错误。对于使用Object而不是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

请注意第一个示例的一些其他问题。而不是简单的ldnull我们有一个无关的initobj调用毫无意义地针对一个多余的局部变量tmp

然而,在here暗示的好消息是,这一切都不重要。尽管上面两个例子生成的IL代码存在差异,但x64 JIT为它们生成了几乎相同的代码。以下结果适用于.NET Framework 4.7.2发布模式,优化为“未抑制”。

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.