¿Por qué necesitamos boxing y unboxing en C#?

Resuelto Vaibhav Jain asked hace 14 años • 12 respuestas

¿Por qué necesitamos boxing y unboxing en C#?

Sé lo que es boxear y unboxing, pero no puedo comprender su uso real. ¿Por qué y dónde debería usarlo?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing
Vaibhav Jain avatar Jan 22 '10 01:01 Vaibhav Jain
Aceptado

Por qué

Tener un sistema de tipos unificado y permitir que los tipos de valor 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, an intes solo un depósito de treinta y dos bits que es completamente diferente a una referencia tipo).

Piensa en esto, de esta manera. Tienes una variable ode tipo object. Y ahora tienes un inty quieres ponerlo o. oes una referencia a algo en algún lugar, y intenfáticamente no es una referencia a algo en algún lugar (después de todo, es solo un número). Entonces, lo que haces es esto: creas un objeto nuevo objectque puede almacenar inty luego asignas una referencia a ese objeto o. A este proceso lo llamamos "boxeo".

Entonces, 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 representar intsu valor subyacente (es decir, en su lugar, también debe inthaber 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 ArrayListsolo come objects. Es decir, sólo almacena referencias a algo que vive en algún lugar. Sin boxeo no puedes incluirlo inten una colección así. Pero con el boxeo sí puedes.

Ahora, en la época de los genéricos, realmente no necesitas esto y, en general, puedes seguir adelante alegremente sin pensar en el tema. 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 double( (double)o) y luego convertirlo en un archivo 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 lo dijiste Truey Falsegenial! ¿Esperar lo? Esto se debe a que ==en los tipos de referencia se utiliza igualdad de referencia, que comprueba 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 la salida? ¡Eso depende! Si Pointes a, structentonces la salida es, 1pero si Pointes a, classentonces la salida es 2. Una conversión de boxeo hace una copia del valor que se encuadra y explica la diferencia de comportamiento.

jason avatar Jan 21 '2010 18:01 jason

En el marco .NET, hay dos tipos de tipos: tipos de valor y tipos de referencia. Esto es relativamente común en los lenguajes OO.

Una de las características importantes de los lenguajes orientados a objetos es la capacidad de manejar instancias de manera independiente del tipo. Esto se conoce como polimorfismo . Como queremos aprovechar el polimorfismo, pero tenemos dos especies diferentes, tiene que haber alguna forma de unirlas para que podamos manejar una u otra de la misma manera.

Ahora, en los viejos tiempos (1.0 de Microsoft.NET), no existía este novedoso alboroto de genéricos. No se podía escribir un método que tuviera un único argumento que pudiera dar servicio a un tipo de valor y un tipo de referencia. Esa es una violación del polimorfismo. Por lo tanto, se adoptó el boxeo como un medio para convertir un tipo de valor en un objeto.

Si esto no fuera posible, el marco estaría plagado de métodos y clases cuyo único propósito era aceptar las otras especies de tipos. No solo eso, sino que dado que los tipos de valor realmente no comparten 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, etc.).

El boxeo impidió que esto sucediera. Y es por eso que los británicos celebran el Boxing Day.

 avatar Jan 21 '2010 18:01

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 solo lugar: la pila. Cada vez que declaras una variable, ésta va a la pila. Solo pueden ser valores primitivos, como bool, byte, int de 32 bits, uint de 32 bits, etc. La pila es simple y rápida. A medida que se agregan variables, simplemente van una encima de otra, por lo que la primera que declara se ubica en, digamos, 0x00, la siguiente en 0x01, la siguiente en 0x02 en RAM, etc. Además, las variables a menudo tienen direcciones previas en la compilación. tiempo, por lo que se conoce su dirección incluso antes de ejecutar el programa.

En el siguiente nivel, como C++, se introduce una segunda estructura de memoria llamada Heap. La mayor parte del tiempo todavía vive 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 montón es un poco desordenado y algo costoso de mantener, porque a diferencia de las variables de pila, no se acumulan linealmente hacia arriba y hacia abajo a medida que se ejecuta un programa. Pueden aparecer y desaparecer sin ningún orden en particular, y pueden crecer y encogerse.

Lidiar con los punteros es difícil. Son la causa de pérdidas de memoria, desbordamientos del 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 mejorar 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 una instancia de una clase van al costoso montón administrado por memoria, mientras que los tipos de valor van a la misma pila que tenía en el nivel C bajo: súper rápido.

Con el fin de mantener simple la interacción entre estos dos conceptos fundamentalmente diferentes de memoria (y estrategias de almacenamiento) 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:

bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!

Un buen ejemplo de la ventaja del boxeo es una verificación de nulo:

if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false

Nuestro objeto o es técnicamente una dirección en el Stack que apunta a una copia de nuestro bool b, que ha sido copiado al Heap. Podemos marcar o para ver si es nulo porque el bool ha sido encajonado y colocado allí.

En general, debes evitar Boxing a menos que lo necesites, por ejemplo, para pasar un int/bool/whatever como objeto a un argumento. Hay algunas estructuras básicas en .Net que aún exigen pasar tipos de valor como objeto (y por lo tanto requieren Boxing), pero en su mayor parte nunca debería necesitar Box.

Una lista no exhaustiva de estructuras históricas de C# que requieren Boxing y que debes evitar:

  • El sistema de eventos resulta tener una condición de carrera si se usa ingenuamente y no admite asíncrono. Agregue el problema del boxeo y probablemente debería evitarse. (Podría reemplazarlo, por ejemplo, con un sistema de eventos asíncrono que use genéricos).

  • Los antiguos modelos Threading y Timer forzaban un Box en sus parámetros, pero han sido reemplazados por async/await, que son mucho más limpios y eficientes.

  • Las colecciones .Net 1.1 se basaron completamente en Boxing, porque vinieron antes que los genéricos. Estos todavía están dando vueltas en System.Collections. En cualquier código nuevo deberías usar las Colecciones de System.Collections.Generic, que además de evitar Boxing también te brindan una mayor seguridad de tipos .

Deberías evitar declarar o pasar tus tipos de valor como objetos, a menos que tengas que lidiar con los problemas históricos anteriores que fuerzan el Boxeo, y quieras evitar el impacto en el rendimiento del Boxeo más adelante, cuando sepas que de todos modos será Boxeado.

Según la sugerencia de Mikael a continuación:

Hacer esto

using System.Collections.Generic;

var employeeCount = 5;
var list = new List<int>(10);

No esta

using System.Collections;

Int32 employeeCount = 5;
var list = new ArrayList(10);

Actualizar

Esta respuesta originalmente sugirió que Int32, Bool, etc. causan boxeo, cuando en realidad son alias simples para tipos de valor. Es decir, .Net tiene tipos como Bool, Int32, String y C# los alias bool, int, string, sin ninguna diferencia funcional.

Chris Moschini avatar Feb 12 '2016 21:02 Chris Moschini

El boxeo no es realmente algo que usted use; es algo que usa el tiempo de ejecución para que pueda manejar tipos de referencia y valores de la misma manera cuando sea necesario. Por ejemplo, si usó ArrayList para contener una lista de números enteros, los enteros se encuadraron para caber en las ranuras de tipo de objeto en ArrayList.

Al usar colecciones genéricas ahora, esto prácticamente desaparece. Si crea un List<int>, no se realiza ningún boxeo: List<int>puede contener los números enteros directamente.

Ray avatar Jan 21 '2010 18:01 Ray

Boxing y Unboxing se utilizan 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ías pasar tipos de valores por referencia; y eso significa que no se pueden pasar tipos de valores como instancias de Objeto.

STW avatar Jan 21 '2010 18:01 STW