Estoy tratando de recopilar todas las situaciones en las que ocurre el boxeo en C #:
Conversión de tipo de valor en
System.Object
tipo:struct S { } object box = new S();
Conversión de tipo de valor en
System.ValueType
tipo:struct S { } System.ValueType box = new S();
Conversión del valor del tipo de enumeración en
System.Enum
tipo: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
char
tipo se concatenan en tiempo de compilaciónNOTA: desde la versión 6.0 de C # compilador optimiza la concatenación que implica
bool
,char
,IntPtr
,UIntPtr
tiposCrear 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
is
expresió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
object
tipo 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
struct
tipo genérico o no restringido con operadoresis
/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?
fuente
private int? nullableInteger
Respuestas:
¡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
fuente
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?base
a un tipo de valor causará boxing. Esto incluye métodos virtuales que no son reemplazados por la estructura y losObject
métodos que no son virtuales en absoluto (comoGetType()
). Vea esta pregunta .ToString
un métodopublic override void ToString() { return base.ToString(); }
y ...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 comoref
pará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 elToString()
método de una estructura y, de ser así, cuál debería ser el tipo de parámetro?Llamando al método GetType () no virtual en el tipo de valor:
struct S { }; S s = new S(); s.GetType();
fuente
GetType
requiere 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" queGetType()
pueda usarse para identificar su tipo.Enum
tener ningún método no virtual propio, aunque unToString()
método para unEnum
necesitaría tener acceso a la información de tipo. Me pregunto siObject
,ValueType
oEnum
tiene algún método no virtual que pueda realizar su trabajo sin información de tipo.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
fuente
System.Collections
tales comoArrayList
oHashTable
.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>
yDictionary<TKey,TValue>
.fuente
Agregar cualquier valor de tipo de valor en ArrayList provoca el encuadre:
ArrayList items = ... numbers.Add(1); // boxing to object
fuente