Ocurrencia de boxeo en C #

85

Estoy tratando de recopilar todas las situaciones en las que ocurre el boxeo en C #:

  • Conversión de tipo de valor en System.Objecttipo:

    struct S { }
    object box = new S();
    
  • Conversión de tipo de valor en System.ValueTypetipo:

    struct S { }
    System.ValueType box = new S();
    
  • Conversión del valor del tipo de enumeración en System.Enumtipo:

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

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

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

    nota: las constantes de chartipo se concatenan en tiempo de compilación

    NOTA: desde la versión 6.0 de C # compilador optimiza la concatenación que implica bool, char, IntPtr, UIntPtrtipos

  • Crear delegado a partir del 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 patrones constantes de C # 7.0 bajo isexpresión:

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

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Parámetros opcionales de objecttipo 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 sin restricciones 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 de structtipo genérico o no restringido con operadores is/ as:

    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, quizás ocultas, que conozcas?

flujo de control
fuente
2
Me ocupé de esto hace algún tiempo y encontré esto bastante interesante: Detectar (des) boxeo usando FxCop
George Duckett
Muy buenas formas de conversión que ha mostrado. De hecho, no sabía el segundo o tal vez nunca lo probé :) Gracias
Zenwalker
12
debería ser una pregunta wiki de la comunidad
Sly
2
¿Qué pasa con los tipos que aceptan valores NULL? private int? nullableInteger
allansson
1
@allansson, los tipos que aceptan valores NULL son solo tipos de valores
controlflow

Respuestas:

42

¡Esa es una gran pregunta!

El boxeo ocurre exactamente por una razón: cuando necesitamos una referencia a un tipo de valor . Todo lo que enumeraste 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, lo que provoca el encuadre.

Si desea enumerar todos los escenarios posibles, también debe incluir derivadas, 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 cadenas que identificó astutamente también se deriva de la conversión al objeto. El compilador traduce el operador + a una llamada al método de cadena Concat, que acepta un objeto para el tipo de valor que usted pasa, por lo que se realiza la conversión al 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 del boxeo (lo especifiqué anteriormente) 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 IL genera el compilador para nuestro código C # (por ejemplo, + on string produce una llamada a String.Concat). Cuando tenga dudas sobre lo que genera el compilador y si se produce un boxeo, puede utilizar 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 se puede producir una caja a pesar de que el IL no incluye el código de operación de la caja, más detalles a continuación).

Pero estoy de acuerdo en que algunos sucesos de boxeo son menos obvios. Enumeró uno de ellos: llamar a un método no anulado de un tipo de valor. De hecho, esto es menos obvio por otra razón: cuando verifica el código IL, no ve el código de operación del cuadro, sino el código de operación de restricción, por lo que incluso en el IL no es obvio que el boxeo ocurre. No entraré en detalles exactos por qué evitar que esta respuesta sea aún más larga ...

Otro caso de 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 anula, por lo que llamar a ToString en MyValType no generará boxing. Sin embargo, la implementación llama a la base ToString y eso provoca el boxeo (¡verifique el IL!).

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

Aquí hay un enlace directo a la sección de mi curso .NET en línea 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 anterior también lo llevará allí una vez que discuta las cosas más básicas): http://motti.me/mu

¡Espero que esto ayude!

Motti

Motti Shaked
fuente
1
Si ToString()se llama a en un tipo de valor particular que no lo anula, ¿el tipo de valor se incluirá en un cuadro en el sitio de la llamada, o el método se enviará (sin encajar) a una anulación generada automáticamente que no hace más que encadenar (con boxeo) al método base?
supercat
@supercat Llamar a cualquier método que llame basea un tipo de valor causará boxing. Esto incluye métodos virtuales que no son reemplazados por la estructura y los Objectmétodos que no son virtuales en absoluto (como GetType()). Vea esta pregunta .
Şafak Gür
@ ŞafakGür: Sé que el resultado final será el boxeo. Me preguntaba sobre el mecanismo exacto a través del cual sucede. Dado que el compilador que genera IL puede no saber si el tipo es un tipo de valor o una referencia (podría ser genérico), generará una callvirt independientemente. El JITter sabría si el tipo es un tipo de valor y si anula ToString, por lo que podría generar código de sitio de llamada para hacer el boxing; alternativamente, podría autogenerarse para cada estructura que no anule ToStringun método public override void ToString() { return base.ToString(); }y ...
supercat
... que el boxeo ocurra dentro de ese método. Dado que el método sería muy corto, entonces podría incluirse. Hacer cosas con el último enfoque permitiría ToString()acceder al método de una estructura a través de Reflection como cualquier otro y usarlo para crear un delegado estático que toma el tipo de estructura como refparámetro [tal cosa funciona con métodos de estructura no heredados], pero yo Intenté crear tal delegado y no funcionó. ¿Es posible crear un delegado estático para el ToString()método de una estructura y, de ser así, cuál debería ser el tipo de parámetro?
supercat
Los enlaces están rotos.
OfirD el
5

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

struct S { };
S s = new S();
s.GetType();
Viacheslav Ivanov
fuente
2
GetTyperequiere boxing no solo porque no es virtual, sino porque las ubicaciones de almacenamiento de tipo de valor, a diferencia de los objetos de montón, no tienen un campo "oculto" que GetType()pueda usarse para identificar su tipo.
supercat
@supercat Hmmm. 1. Boxing agregado por el compilador y campo oculto usado por el tiempo de ejecución. Puede ser que el compilador agregue boxing porque conoce el tiempo de ejecución ... 2. Cuando llamamos a ToString no virtual (cadena) en el valor de enumeración, también requiere boxing y no creo que el compilador agregue esto porque conoce los detalles de implementación de Enum.ToString (string) . Por lo tanto, creo que puedo decir que el boxeo siempre ocurrió cuando se llamó al método no virtual en "tipo de valor base".
Viacheslav Ivanov
No había considerado Enumtener ningún método no virtual propio, aunque un ToString()método para un Enumnecesitaría tener acceso a la información de tipo. Me pregunto si Object, ValueTypeo Enumtiene algún método no virtual que pueda realizar su trabajo sin información de tipo.
supercat
3

Mencionado en la respuesta de Motti, solo ilustrando con ejemplos 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 retorno

public object Bla()
{
    return 1;
}

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

Comprobación de 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

nawfal
fuente
0
  • Usando las colecciones no genéricas en System.Collectionstales como ArrayListo HashTable.

De acuerdo, estos son casos específicos de su primer caso, pero pueden ser trampas ocultas. Es sorprendente la cantidad de código que todavía encuentro hoy que usa estos en lugar de List<T>y Dictionary<TKey,TValue>.

Jesse C. Rebanadora
fuente
0

Agregar cualquier valor de tipo de valor en ArrayList provoca el encuadre:

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