Pregunta Ocurrencia del boxeo en C #


Intento recopilar todas las situaciones en las que se produce el boxeo en C #:

  • Convertir el tipo de valor a System.Object tipo:

    struct S { }
    object box = new S();
    
  • Convertir el tipo de valor a System.ValueType tipo:

    struct S { }
    System.ValueType box = new S();
    
  • Convertir el valor del tipo de enumeración a System.Enum tipo:

    enum E { A }
    System.Enum box = E.A;
    
  • Conversión de tipo de valor en referencia de interfaz:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • Usar tipos de valores en la concatenación de cadenas C #:

    char c = F();
    string s1 = "char value will box" + c;
    

    Nota: constantes de char tipo se concatenan en tiempo de compilación

    Nota: desde el compilador de la versión 6.0 C # optimiza la concatenación involucrando bool, char, IntPtr, UIntPtr tipos

  • Crear un delegado desde el método de instancia de tipo de valor:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Llamar a métodos virtuales no anulados en tipos de valor:

    enum E { A }
    E.A.GetHashCode();
    
  • Usando C # 7.0 patrones constantes bajo is expresión:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • Boxeo en conversiones de tipos de tuplas de C #:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Parámetros opcionales de object escriba con valores predeterminados de tipo de valor:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • Comprobando el valor del tipo genérico no restringido para null:

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    Nota: esto puede ser optimizado por JIT en algunos tiempos de ejecución .NET

  • Escriba el valor de prueba sin restricciones o struct tipo genérico con is/as operadores:

    bool M<T>(T t) => t is int;
    int? M<T>(T t) => t as int?;
    IEquatable<T> M<T>(T t) => t as IEquatable<T>;
    M(42);
    

    Nota: esto puede ser optimizado por JIT en algunos tiempos de ejecución .NET

¿Hay más situaciones de boxeo, tal vez ocultas, que usted sepa?


76
2017-11-03 13:22


origen


Respuestas:


¡Esa es una gran pregunta!

El boxeo ocurre por exactamente una razón: cuando necesitamos una referencia a un tipo de valor. Todo lo que enumeró cae en esta regla.

Por ejemplo, dado que el objeto es un tipo de referencia, la conversión de un tipo de valor a un objeto requiere una referencia a un tipo de valor, que causa el boxeo.

Si desea enumerar todos los escenarios posibles, también debe incluir derivados, como devolver un tipo de valor de un método que devuelve un objeto o un tipo de interfaz, porque esto arroja automáticamente el tipo de valor al objeto / interfaz.

Por cierto, el caso de concatenación de cuerdas que astutamente identificas también se deriva de la conversión al objeto. El operador + es traducido por el compilador a una llamada al método de cadena Concat, que acepta un objeto para el tipo de valor que pasa, por lo que se convierte en objeto y, por lo tanto, se produce el boxeo.

A lo largo de los años, siempre he aconsejado a los desarrolladores que recuerden la única razón para el boxeo (especifiqué más arriba) en lugar de memorizar cada caso, porque la lista es larga y difícil de recordar. Esto también promueve la comprensión de qué código de IL genera el compilador para nuestro código C # (por ejemplo, + en una cadena produce una llamada a String.Concat). Cuando tienes dudas sobre lo que genera el compilador y si ocurre el boxeo, puedes usar IL Disassembler (ILDASM.exe). Por lo general, debe buscar el código de operación de la caja (solo hay un caso en el que puede haber boxeo, aunque IL no incluya el código de operación de la caja, más detalles a continuación).

Pero estoy de acuerdo en que algunas ocurrencias de boxeo son menos obvias. Ha enumerado uno de ellos: llamar a un método no reemplazado de un tipo de valor. De hecho, esto es menos obvio por otra razón: cuando revisas el código IL no ves el código de operación de la caja, sino el código de operación de restricción, ¡así que incluso en la IL no es obvio que el boxeo ocurra! No entraré en los detalles exactos de por qué para evitar que esta respuesta sea aún más larga ...

Otro caso para el boxeo menos obvio es cuando se llama a un método de clase base desde una estructura. Ejemplo:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

Aquí ToString se reemplaza, por lo que llamar a ToString en MyValType no generará boxeo. Sin embargo, la implementación llama a la base ToString y eso causa el boxeo (¡compruebe el IL!).

Por cierto, estos dos escenarios de boxeo no obvios también se derivan de la única regla anterior. Cuando se invoca un método en la clase base de un tipo de valor, debe haber algo para el esta palabra clave para referirse a. Como la clase base de un tipo de valor es (siempre) un tipo de referencia, el esta la palabra clave debe hacer referencia a un tipo de referencia, por lo que necesitamos una referencia a un tipo de valor y, por lo tanto, el boxeo se produce debido a la regla única.

Aquí hay un enlace directo a la sección de mi curso en línea de .NET que analiza el boxeo en detalle: http://motti.me/mq

Si solo está interesado en escenarios de boxeo más avanzados, aquí hay un enlace directo allí (aunque el enlace de arriba también lo llevará allí una vez que discute las cosas más básicas): http://motti.me/mu

¡Espero que esto ayude!

Motti


40
2017-11-03 15:32



Llamando al método GetType () no virtual en el tipo de valor:

struct S { };
S s = new S();
s.GetType();

5
2017-11-03 15:20



Mencionado en la respuesta de Motti, simplemente ilustrando con muestras de código:

Parámetros involucrados

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

Pero esto es seguro

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

Tipo de devolución

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

Comprobando T sin restricciones contra nulo

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

Uso de dinámica

dynamic x = 42; (boxes)

Otro

enumValue.HasFlag


1
2018-04-16 09:49



  • Usando las colecciones no genéricas en System.Collections como ArrayList o HashTable.

De acuerdo, estas son instancias específicas de su primer caso, pero pueden ser errores ocultos. Es increíble la cantidad de código que todavía encuentro hoy en día que usan estos en lugar de List<T> y Dictionary<TKey,TValue>.


0
2017-11-03 15:40



Agregar cualquier valor de tipo de valor en ArrayList causa el boxeo:

ArrayList items = ...
numbers.Add(1); // boxing to object

0
2017-11-03 21:51