Почему универсальный метод с ограничением класса T: приводит к боксу?
Почему универсальный метод, который ограничивает T для класса, будет иметь инструкции по боксу в генерирующем коде MSIL?
Я был весьма удивлен этим, так как, конечно, поскольку T ограничен ссылочным типом, сгенерированный код не должен выполнять какую-либо упаковку.
Вот код С#:
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 инструкции.
Почему это генерируется?
Как этого избежать?
Ответы
Ответ 1
Вам не нужно беспокоиться о каких-либо ухудшениях производительности от команды box
, потому что если его аргумент является ссылочным типом, команда box
ничего не делает. Хотя все еще странно, что команда box
даже была создана (возможно, ленивость/более простой дизайн при генерации кода?).
Ответ 2
Я не уверен, почему какой-то бокс идет. Один из возможных способов избежать бокса - не использовать его. Просто перекомпилируйте без бокса. Пример:
.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");
... конечно, я изменил его с защищенного на публичный, вам нужно будет снова внести изменения и предоставить остальную часть вашей реализации.
Ответ 3
Я считаю, что это предусмотрено дизайном. Вы не ограничиваете T конкретным классом, чтобы он, скорее всего, сбрасывал его на объект. Следовательно, почему вы видите, что IL включает бокс.
Я бы попробовал этот код, где T: ActualClass
Ответ 4
Вслед за парой пунктов. Прежде всего, эта ошибка возникает для обоих методов в универсальном классе с ограничением, 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
.
Хорошая новость, намекаемая здесь, заключается в том, что все это не имеет значения. Несмотря на различия в коде IL, сгенерированном для двух приведенных выше примеров, JIT x64 генерирует для них практически идентичный код. Следующий результат относится к режиму выпуска.NET Framework 4.7.2 с оптимизацией "не подавлено".
![enter image description here]()