number - value types in c#
Ocurrencia del boxeo en C# (5)
¡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 que se refiera a esta palabra clave. Dado que la clase base de un tipo de valor es (siempre) un tipo de referencia, esta palabra clave debe referirse 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 .NET en línea que trata 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
Intento recopilar todas las situaciones en las que se produce el boxeo en C #:
Conversión de tipo de valor a tipo
System.Object
:struct S { } object box = new S();
Conversión de tipo de valor a tipo
System.ValueType
: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: las constantes del tipo de
char
se concatenan en tiempo de compilaciónNota: desde la versión 6.0 el compilador de C # optimiza la concatenación que involucra los tipos
bool
,char
,IntPtr
,UIntPtr
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();
El uso de patrones constantes C # 7.0 en
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 tipo de
object
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 de tipo genérico sin restricción o
struct
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, tal vez ocultas, que usted sepa?
Agregar cualquier valor de tipo de valor en ArrayList causa el boxeo:
ArrayList items = ...
numbers.Add(1); // boxing to object
Llamando al método GetType () no virtual en el tipo de valor:
struct S { };
S s = new S();
s.GetType();
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
- Usar las colecciones no genéricas en
System.Collections
comoArrayList
oHashTable
.
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 aún encuentro hoy en día que usan estos en lugar de List<T>
y Dictionary<TKey,TValue>
.