Por qué
Tener un sistema de tipos unificado y permitir que los tipos de valores tengan una representación completamente diferente de sus datos subyacentes de la forma en que los tipos de referencia representan sus datos subyacentes (por ejemplo, un int
es solo un grupo de treinta y dos bits que es completamente diferente a una referencia tipo).
Piensa en esto, de esta manera. Tienes una variable o
de tipo object
. Y ahora tienes un int
y quieres ponerlo o
. o
es una referencia a algo en alguna parte, y int
enfáticamente no es una referencia a algo en alguna parte (después de todo, es solo un número). Entonces, lo que haces es esto: haces una nueva object
que puede almacenar int
y luego le asignas una referencia a ese objeto o
. A este proceso lo llamamos "boxeo".
Por lo tanto, si no le importa tener un sistema de tipos unificado (es decir, los tipos de referencia y los tipos de valores tienen representaciones muy diferentes y no desea una forma común de "representar" los dos), entonces no necesita boxeo. Si no le importa int
representar su valor subyacente (es decir, también int
debe ser tipos de referencia y simplemente almacenar una referencia a su valor subyacente), entonces no necesita boxeo.
¿Dónde debería usarlo?
Por ejemplo, el tipo de colección anterior ArrayList
solo come object
s. Es decir, solo almacena referencias a algo que vive en algún lugar. Sin el boxeo, no se puede poner una int
en tal colección. Pero con el boxeo, puedes.
Ahora, en los días de los genéricos, realmente no se necesita esto y, en general, se puede seguir alegremente sin pensar en el problema. Pero hay algunas advertencias a tener en cuenta:
Esto es correcto:
double e = 2.718281828459045;
int ee = (int)e;
Esto no es:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
En su lugar, debes hacer esto:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
Primero tenemos que desempaquetar explícitamente el double
( (double)o
) y luego transmitirlo a un int
.
¿Cuál es el resultado de lo siguiente?
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
Piénselo por un segundo antes de pasar a la siguiente oración.
Si dijiste True
y False
genial! ¿Esperar lo? Esto se debe a que ==
en los tipos de referencia se usa la igualdad de referencia que verifica si las referencias son iguales, no si los valores subyacentes son iguales. Este es un error peligrosamente fácil de cometer. Quizás aún más sutil
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
también se imprimirá False
!
Mejor decir:
Console.WriteLine(o1.Equals(o2));
que luego, afortunadamente, se imprimirá True
.
Una última sutileza:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
¿Cuál es el resultado? ¡Depende! Si Point
es a, struct
entonces la salida es 1
pero si Point
es a, ¡ class
entonces la salida es 2
! Una conversión de boxeo hace una copia del valor que se está boxeando y explica la diferencia en el comportamiento.
boxing
yunboxing
?En el marco .NET, hay dos especies de tipos: tipos de valor y tipos de referencia. Esto es relativamente común en los idiomas OO.
Una de las características importantes de los lenguajes orientados a objetos es la capacidad de manejar instancias de una manera independiente del tipo. Esto se conoce como polimorfismo . Dado que queremos aprovechar el polimorfismo, pero tenemos dos especies diferentes de tipos, tiene que haber alguna forma de unirlos para que podamos manejar uno u otro de la misma manera.
Ahora, en los viejos tiempos (1.0 de Microsoft.NET), no había este nuevo alboroto genérico. No podría escribir un método que tuviera un único argumento que pudiera servir un tipo de valor y un tipo de referencia. Eso es una violación del polimorfismo. Entonces, el boxeo fue adoptado como un medio para forzar un tipo de valor en un objeto.
Si esto no fuera posible, el marco estaría lleno de métodos y clases cuyo único propósito era aceptar las otras especies de tipo. No solo eso, sino que dado que los tipos de valor no comparten realmente un ancestro de tipo común, tendría que tener una sobrecarga de método diferente para cada tipo de valor (bit, byte, int16, int32, etc., etc.).
El boxeo evitó que esto sucediera. Y es por eso que los británicos celebran el día del boxeo.
fuente
List<string>.Enumerator
paraIEnumerator<string>
producir un objeto que se comporta principalmente como un tipo de clase, pero con unEquals
método roto . Una mejor manera de fundiciónList<string>.Enumerator
aIEnumerator<string>
sería llamar a un operador de conversión personalizada, pero la existencia de un evita la conversión implícita de que.La mejor manera de entender esto es observar los lenguajes de programación de nivel inferior en los que se basa C #.
En los lenguajes de nivel más bajo como C, todas las variables van a un lugar: The Stack. Cada vez que declaras una variable, va a la Pila. Solo pueden ser valores primitivos, como un bool, un byte, un int de 32 bits, un uint de 32 bits, etc. El Stack es simple y rápido. A medida que se agregan variables, simplemente van una encima de otra, por lo que la primera que declara se encuentra en digamos, 0x00, la siguiente en 0x01, la siguiente en 0x02 en RAM, etc. Además, las variables a menudo se direccionan previamente en la compilación. tiempo, por lo que su dirección se conoce incluso antes de ejecutar el programa.
En el siguiente nivel, como C ++, se introduce una segunda estructura de memoria llamada Heap. Todavía vive principalmente en la Pila, pero se pueden agregar entradas especiales llamadas Punteros a la Pila, que almacenan la dirección de memoria para el primer byte de un Objeto, y ese Objeto vive en el Montón. El Heap es un desastre y es algo costoso de mantener, porque a diferencia de las variables de Stack, no se acumulan linealmente hacia arriba y hacia abajo a medida que se ejecuta un programa. Pueden ir y venir sin una secuencia particular, y pueden crecer y encogerse.
Tratar con punteros es difícil. Son la causa de pérdidas de memoria, desbordamientos de búfer y frustración. C # al rescate.
En un nivel superior, C #, no necesita pensar en punteros: el marco .Net (escrito en C ++) piensa en estos por usted y se los presenta como referencias a objetos, y para el rendimiento, le permite almacenar valores más simples como bools, bytes e ints como tipos de valor. Debajo del capó, los Objetos y las cosas que crean instancias de una Clase van en el Montón costoso administrado por la memoria, mientras que los Tipos de valor van en la misma Pila que tenía en C de bajo nivel: súper rápido.
En aras de mantener la interacción entre estos 2 conceptos fundamentalmente diferentes de memoria (y estrategias de almacenamiento) simples desde la perspectiva de un codificador, los Tipos de valor se pueden encuadrar en cualquier momento. El boxeo hace que el valor se copie de la Pila, se coloque en un Objeto y se coloque en el Montón , una interacción más costosa pero fluida con el mundo de Referencia. Como señalan otras respuestas, esto ocurrirá cuando, por ejemplo, diga:
Una buena ilustración de la ventaja del boxeo es un cheque por nulo:
Nuestro objeto o es técnicamente una dirección en la pila que apunta a una copia de nuestro bool b, que se ha copiado en el montón. Podemos marcar o para nulo porque el bool ha sido encajonado y puesto allí.
En general, debe evitar el Boxeo a menos que lo necesite, por ejemplo, para pasar un int / bool / lo que sea como un objeto a un argumento. Hay algunas estructuras básicas en .Net que todavía exigen pasar Tipos de valor como objeto (y por lo tanto requieren Boxeo), pero en su mayor parte nunca debería necesitar Box.
Una lista no exhaustiva de estructuras históricas de C # que requieren Boxeo, que debe evitar:
El sistema de eventos resulta tener una condición de carrera en el uso ingenuo de él, y no admite asíncrono. Agregue el problema de Boxeo y probablemente debería evitarse. (Podría reemplazarlo, por ejemplo, con un sistema de eventos asíncrono que utiliza genéricos).
Los antiguos modelos Threading y Timer forzaron un Box en sus parámetros, pero han sido reemplazados por async / wait, que son mucho más limpios y más eficientes.
Las colecciones .Net 1.1 se basaron completamente en el boxeo, porque llegaron antes que los genéricos. Todavía están dando vueltas en System.Collections. En cualquier código nuevo, debe usar las Colecciones de System.Collections.Generic, que además de evitar el Boxeo también le brindan una mayor seguridad de escritura .
Debe evitar declarar o pasar sus Tipos de valor como objetos, a menos que tenga que lidiar con los problemas históricos anteriores que fuerzan el Boxeo, y desea evitar el impacto de rendimiento de Boxeo más adelante cuando sabe que de todos modos se va a Boxear.
Según la sugerencia de Mikael a continuación:
Hacer esto
No esta
Actualizar
Esta respuesta originalmente sugirió que Int32, Bool, etc. causaran boxeo, cuando en realidad son alias simples para los Tipos de valor. Es decir, .Net tiene tipos como Bool, Int32, String y C # los alias a bool, int, string, sin ninguna diferencia funcional.
fuente
El boxeo no es realmente algo que usa, es algo que usa el tiempo de ejecución para que pueda manejar los tipos de referencia y valor de la misma manera cuando sea necesario. Por ejemplo, si usó una ArrayList para contener una lista de enteros, los enteros se encuadraron para encajar en las ranuras de tipo de objeto en la ArrayList.
Usando colecciones genéricas ahora, esto prácticamente desaparece. Si crea un
List<int>
, no se realiza ningún boxeo;List<int>
puede contener los enteros directamente.fuente
Boxing y Unboxing se usan específicamente para tratar objetos de tipo valor como tipo de referencia; moviendo su valor real al montón administrado y accediendo a su valor por referencia.
Sin boxing y unboxing, nunca podría pasar los tipos de valor por referencia; y eso significa que no puede pasar tipos de valores como instancias de Object.
fuente
El último lugar donde tuve que desempaquetar algo fue cuando escribí un código que recuperaba algunos datos de una base de datos (no estaba usando LINQ to SQL , simplemente el viejo ADO.NET ):
Básicamente, si está trabajando con API anteriores antes de los genéricos, se encontrará con el boxeo. Aparte de eso, no es tan común.
fuente
Se requiere el boxeo, cuando tenemos una función que necesita un objeto como parámetro, pero tenemos diferentes tipos de valores que deben pasarse, en ese caso necesitamos convertir primero los tipos de valores en tipos de datos de objetos antes de pasarlos a la función.
No creo que sea cierto, intente esto en su lugar:
Eso funciona bien, no utilicé boxing / unboxing. (¿A menos que el compilador haga eso detrás de escena?)
fuente
En .net, cada instancia de Object, o cualquier tipo derivado de ella, incluye una estructura de datos que contiene información sobre su tipo. Los tipos de valores "reales" en .net no contienen dicha información. Para permitir que los datos en los tipos de valores sean manipulados por rutinas que esperan recibir tipos derivados de objetos, el sistema define automáticamente para cada tipo de valor un tipo de clase correspondiente con los mismos miembros y campos. El boxeo crea nuevas instancias de este tipo de clase, copiando los campos de una instancia de tipo de valor. Unboxing copia los campos de una instancia del tipo de clase a una instancia del tipo de valor. Todos los tipos de clase que se crean a partir de tipos de valor se derivan de la clase ValueType llamada irónicamente (que, a pesar de su nombre, es en realidad un tipo de referencia).
fuente
Cuando un método solo toma un tipo de referencia como parámetro (por ejemplo, un método genérico restringido a ser una clase a través de la
new
restricción), no podrá pasarle un tipo de referencia y tener que encajonarlo.Esto también es cierto para cualquier métodos que toman
object
como un parámetro - esto tiene que ser un tipo de referencia.fuente
En general, generalmente querrá evitar el encajonamiento de sus tipos de valor.
Sin embargo, hay casos raros en los que esto es útil. Si necesita apuntar al marco 1.1, por ejemplo, no tendrá acceso a las colecciones genéricas. Cualquier uso de las colecciones en .NET 1.1 requeriría tratar su tipo de valor como un System.Object, lo que causa el boxing / unboxing.
Todavía hay casos para que esto sea útil en .NET 2.0+. Cada vez que desee aprovechar el hecho de que todos los tipos, incluidos los tipos de valor, se pueden tratar como un objeto directamente, es posible que deba usar el boxing / unboxing. Esto puede ser útil a veces, ya que le permite guardar cualquier tipo en una colección (mediante el uso de objetos en lugar de T en una colección genérica), pero en general, es mejor evitar esto, ya que está perdiendo la seguridad de los tipos. Sin embargo, el único caso en el que el boxeo ocurre con frecuencia es cuando usa Reflection: muchas de las llamadas en reflexión requerirán boxing / unboxing cuando trabaje con tipos de valor, ya que el tipo no se conoce de antemano.
fuente
El boxeo es la conversión de un valor a un tipo de referencia con los datos en algún desplazamiento en un objeto en el montón.
En cuanto a lo que realmente hace el boxeo. Aquí hay unos ejemplos
Mono C ++
Unboxing en Mono es un proceso de lanzar un puntero en un desplazamiento de 2 gpointers en el objeto (por ejemplo, 16 bytes). A
gpointer
es avoid*
. Esto tiene sentido cuando se mira la definición de,MonoObject
ya que claramente es solo un encabezado para los datos.C ++
Para boxear un valor en C ++, podría hacer algo como:
fuente