¿Por qué el método genérico con la restricción de la clase T: resulta en el boxeo? [duplicar]
Pregunta
Esta pregunta ya tiene una respuesta aquí:
- Boxeo cuando se usan genéricos en C # 2 respuestas
¿Por qué un método genérico que restringe T a clase tendría instrucciones de boxeo en el código MSIL generado?
Esto me sorprendió bastante, ya que seguramente, dado que T se está restringiendo a un tipo de referencia, el código generado no debería necesitar ningún tipo de boxeo.
Aquí está el código 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;
}
}
Aquí está la IL generada:
.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<*>)
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
}
Observe el cuadro !! T instrucciones.
¿Por qué se está generando esto?
¿Cómo evitar esto?
Solución
No tiene que preocuparse por ninguna degradación del rendimiento de la instrucción box
porque si su argumento es un tipo de referencia, la instrucción box
no hace nada. Aunque aún es extraño que la instrucción box
haya sido creada (¿quizás perezoso / más fácil de diseñar en la generación de código?).
Otros consejos
No estoy seguro de por qué está ocurriendo ningún boxeo. Una posible forma de evitar el boxeo es no usarlo. Sólo recompilar sin el boxeo. Ej:
.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 FixedPBF TestFixedPBF = new FixedPBF();
TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");
)
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
}
}
... si guarda en un archivo recomp_srp.msil, simplemente puede recompilarlo como tal:
ildasm / dll recomp_srp.msil
Y se ejecuta bien sin el boxeo en mi extremo:
<*>... por supuesto, lo cambié de protegido a público, deberías hacer el cambio nuevamente y proporcionar el resto de tu implementación.
Creo que esto está pensado por diseño. No estás restringiendo la T a una clase específica, por lo que lo más probable es que sea un error. Por lo tanto, ¿por qué ves que IL incluye el boxeo?
Intentaría este código con donde T: ActualClass
Seguimiento de un par de puntos. En primer lugar, este error se produce para ambos métodos en una clase genérica con restricción donde T: class
y también métodos genéricos con la misma restricción ( en una clase genérica o no genérica). No se produce para un método no genérico (de lo contrario idéntico) que utiliza Object
en lugar de 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
Note algunos problemas adicionales con el primer ejemplo. En lugar de simplemente ldnull
tenemos una llamada initobj
extraña que apunta sin sentido a un exceso de variable local tmp
.
Sin embargo, la buena noticia, que se insinuó en aquí , es que nada de esto importa. A pesar de las diferencias en el código IL generado para los dos ejemplos anteriores, el x64 JIT genera un código casi idéntico para ellos. El siguiente resultado es para .NET Framework 4.7.2 modo de lanzamiento con optimización " no suprimido " ;.