¿Por qué el método genérico con la restricción de la clase T: resulta en el boxeo? [duplicar]

StackOverflow https://stackoverflow.com/questions/1400414

  •  05-07-2019
  •  | 
  •  

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?

¿Fue útil?

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 " ;.

 ingrese la descripción de la imagen aquí

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top