为什么将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 说明。
为什么要生成这个?
如何避免这种情况?
答案 0 :(得分:2)
您不必担心box
指令会导致任何性能下降,因为如果其参数是引用类型,则box
指令不执行任何操作。尽管box
指令甚至已经被创建(在代码生成时可能是懒惰/更容易设计)仍然很奇怪。)
答案 1 :(得分: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 / dll recomp_srp.msil
在没有拳击的情况下它运行正常:
FixedPBF TestFixedPBF = new FixedPBF();
TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");
...当然,我将其从受保护更改为公开,您需要再次进行更改并提供其余的实施。
答案 2 :(得分:0)
我认为这是设计意图。你没有将T限制在一个特定的类中,因此很可能将它强制转换为对象。因此,为什么你看到IL包括拳击。
我会尝试使用T:ActualClass
的代码答案 3 :(得分: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
。
然而,here暗示的好消息是,这些都不重要。尽管上面两个示例生成的IL代码有所不同,但 x64 JIT 为其生成的代码几乎相同。以下结果是针对具有优化“未抑制”功能的.NET Framework 4.7.2 发布模式的。