Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/322.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么带有T:class约束的泛型方法会导致装箱?_C#_.net_Generics_Boxing - Fatal编程技术网

C# 为什么带有T:class约束的泛型方法会导致装箱?

C# 为什么带有T:class约束的泛型方法会导致装箱?,c#,.net,generics,boxing,C#,.net,Generics,Boxing,为什么将T约束为类的泛型方法在生成MSIL代码时会有装箱指令 我对此感到非常惊讶,因为T被约束为引用类型,所以生成的代码不需要执行任何装箱 以下是c#代码: 受保护的void SetRefProperty(ref T propertyBackingField,T newValue),其中T:class { bool isDifferent=false; //对于引用类型,我们使用简单的引用相等性检查来确定 //值是否“相等”。我们不使用相等比较器,因为它们通常是相等的 //不可靠的相等指标,因为

为什么将T约束为类的泛型方法在生成MSIL代码时会有装箱指令

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

以下是c#代码:

受保护的void SetRefProperty(ref T propertyBackingField,T newValue),其中T:class
{
bool isDifferent=false;
//对于引用类型,我们使用简单的引用相等性检查来确定
//值是否“相等”。我们不使用相等比较器,因为它们通常是相等的
//不可靠的相等指标,因为值相等并不表示
//我们应该共享一个引用类型,因为它可能是可变的。
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 
}
.method family hidebysing实例void SetRefProperty(!!T&propertyBackingField,!!T newValue)cil托管
{
.maxstack 2
.init(
[0]布尔是不同的,
[1] bool CS$4$0000)
L_0000:没有
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:没有
L_001b:ldc.i4.1
L_001c:stloc.0
L_001d:没有
L_001e:ret
}
注意框!!T说明

为什么会产生这种情况


如何避免这种情况?

我相信这是有意为之。您没有将T约束到特定的类,所以它很可能将其向下转换为object。因此,你看到的IL包括拳击


我会在where T:ActualClass中尝试这段代码,我不知道为什么会出现拳击。避免拳击的一个可能方法是不要使用它。只需重新编译而无需装箱。例:

.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

}

}
.assembly recomp\u srp
{
.版本1:0:0:0
}
.class公共自动ansi FixedPBF
{
.method公共实例void.ctor()cil托管
{
}
.method hidebysing public instance void SetRefProperty(!!T&propertyBackingField,!!T newValue)cil managed
{
.maxstack 2
.locals init(bool不同,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/dll recomp_srp.msil

我这边没有拳击,它跑得还可以:

        FixedPBF TestFixedPBF = new FixedPBF();

        TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");
FixedPBF TestFixedPBF=newfixedpbf();
SetRefProperty(ref TestField,“test2”);

…当然,我将其从protected更改为public,您需要再次进行更改,并提供其余的实现。

您不必担心
指令的任何性能下降,因为如果其参数是引用类型,则
指令不起任何作用。尽管仍然很奇怪,
指令甚至被创建出来了(可能是懒散/代码生成时的设计更简单?。

接下来是几点。首先,对于具有约束的泛型类中的两种方法(其中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
//静态T XchgNullCur(ref T addr,T value),其中T:class=>
//联锁。比较交换(参考地址、值、空)??价值
.locals init(!T tmp)
ldarg地址
ldarg val
ldloca tmp
initobj!T
低密度脂蛋白胆固醇
呼叫0联锁::比较交换(!!0&,!!0,!!0)
重复
盒子!T
brtruel001a
流行音乐
ldarg val
L_001a:
ret
//静态对象XchgNullCur(参考对象地址,对象值)=>
//联锁。比较交换(参考地址、值、空)??价值
ldarg地址
ldarg val
ldnull
调用对象联锁::比较交换(对象&,对象,对象)
重复
brtrue L_000d
流行音乐
ldarg val
L_000d:
ret
注意第一个示例中的一些附加问题。除了简单的
ldnull
之外,我们还有一个无关的
initobj
调用,它无意义地针对一个多余的局部变量
tmp

然而,有人暗示,好消息是这些都无关紧要。尽管为上述两个示例生成的IL代码存在差异,但x64 JIT生成的代码几乎相同。以下结果适用于.NET Framework 4.7.2发布模式,优化为“未抑制”


如果您不使用T:ActualClass,为什么还要使用泛型呢?因为您可以将T约束到更高的级别。。。就像iSomeInterface…Chris,如果T是一个对象,它在推堆栈之前不是已经被装箱了吗?那么,为什么需要对其执行装箱操作呢?如果T是一个对象,我希望==运算符检查引用相等性,因此这也不需要un/装箱操作。@RobertHarvey只是为了平息这场长达10年的争论,同时也为了确认这种不幸的C#行为在2019年仍然盛行,无根据的装箱说明a̲r̲e̲i̲n̲d̲e̲e̲d̲s̲t̲l̲e̲m̲i̲t̲e̲d̲d̲d̲即使在约束到某个派生类
时,其中t:lclass
指定了与后一类相对应的实际代码类型(注意——不是
System.Object
,它是
System.ValueType
从理论上推导出来的)
// 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