问题
Why a generic method which constrains T to class would have boxing instructions in the generates MSIL code?
I was quite surprised by this since surely since T is being constrained to a reference type the generated code should not need to perform any boxing.
Here is the c# code:
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;
}
}
Here is the generated 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
}
Notice the box !!T instructions.
Why this is being generated?
How to avoid this?
回答1:
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?).
回答2:
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.
回答3:
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
回答4:
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".
来源:https://stackoverflow.com/questions/1400414/why-does-generic-method-with-constraint-of-t-class-result-in-boxing