una tipos tipo puede otro implícitamente implicita falta explícita existe dinamico datos dato convertir conversión conversiones conversion compruebe como bool c# .net boxing

tipos - ¿Por qué necesitamos boxeo y unboxing en C#?



tipo de dato dinamico c# (10)

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

Sé lo que es el boxeo y el 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


Por qué

Para tener un sistema de tipo 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, un int es solo un grupo de treinta y dos bits que es completamente diferente a un tipo de referencia).

Piensa en esto, de esta manera. Tienes una variable o de tipo object . Y ahora tienes un int y quieres ponerlo en o . o es una referencia a algo en algún lugar, y el int es enfáticamente no 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 nuevo object que puede almacenar el int y luego asignas una referencia a ese objeto a o . Llamamos a este proceso "boxeo".

Por lo tanto, si no te importa tener un sistema de tipo unificado (es decir, los tipos de referencia y los tipos de valor tienen representaciones muy diferentes y no quieres una forma común de "representar" a los dos), entonces no necesitas boxear. Si no le importa que int represente su valor subyacente (es decir, en su lugar, int sean tipos de referencia y simplemente almacene una referencia a su valor subyacente), entonces no necesita boxeo.

donde debo usarlo

Por ejemplo, el antiguo tipo de colección ArrayList solo come los object . Es decir, solo almacena referencias a cosas que viven en algún lugar. Sin el boxeo no se puede poner un int en dicha colección. Pero con el boxeo, puedes.

Ahora, en los días de los genéricos, realmente no necesitas esto y generalmente puedes ir 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, debe 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 convertirlo en 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 dijeras True y False genial! ¿Esperar lo? Esto se debe a que == en los tipos de referencia utiliza 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, imprime 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? ¡Depende! Si Point es una struct entonces la salida es 1 pero si Point es una class entonces la salida es 2 ! Una conversión de boxeo hace una copia del valor del recuadro explicando la diferencia de comportamiento.


Cuando un método solo toma un tipo de referencia como parámetro (por ejemplo, un método genérico restringido para ser una clase a través de la new restricción), no podrá pasarle un tipo de referencia y tendrá que encasillarlo.

Esto también es válido para cualquier método que tome el object como parámetro; tendrá que ser un tipo de referencia.


El último lugar en el que tuve que desempaquetar algo fue cuando escribí un código que recuperó algunos datos de una base de datos (no estaba usando LINQ to SQL , simplemente ADO.NET antiguo):

int myIntValue = (int)reader["MyIntValue"];

Básicamente, si estás trabajando con API más antiguas antes de los genéricos, encontrarás boxeo. Aparte de eso, no es tan común.


El boxeo es obligatorio, cuando tenemos una función que necesita un objeto como parámetro, pero tenemos que pasar diferentes tipos de valores, en ese caso primero debemos convertir los tipos de valores en tipos de datos de objetos antes de pasarlos a la función.

No creo que eso sea cierto, intente esto en su lugar:

class Program { static void Main(string[] args) { int x = 4; test(x); } static void test(object o) { Console.WriteLine(o.ToString()); } }

Eso funciona bien, no usé boxeo / desempaquetado. (A menos que el compilador haga eso detrás de las escenas?)


El boxeo no es realmente algo que use, es algo que utiliza 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ó un ArrayList para mantener una lista de enteros, los enteros se encuadraron para encajar en las ranuras de tipo objeto en el ArrayList.

Usando colecciones genéricas ahora, esto desaparece. Si crea una List<int> , no hay boxeo hecho, la List<int> puede contener los números enteros directamente.


El boxeo y Unboxing se utilizan específicamente para tratar los objetos de tipo valor como tipo de referencia; moviendo su valor real al montón administrado y accediendo a su valor por referencia.

Sin el boxeo y el desempaquetado nunca podrías pasar tipos de valor por referencia; y eso significa que no podría pasar tipos de valor como instancias de Object.


En .net, cada instancia de Object, o cualquier tipo derivado del mismo, incluye una estructura de datos que contiene información sobre su tipo. Los tipos de valor "reales" en .net no contienen ninguna información de este tipo. Para permitir que los datos en 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 una nueva instancia 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 con nombre irónicamente ValueType (que, a pesar de su nombre, es en realidad un tipo de referencia).


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 no tipográfica. Esto se conoce como polymorphism . Ya que queremos aprovechar el polimorfismo, pero tenemos dos especies diferentes de tipos, tiene que haber alguna manera de unirlos para que podamos manejar una u otra de la misma manera.

Ahora, en los días pasados ​​(1.0 de Microsoft.NET), no existían estos nuevos genéricos de hullabaloo. No se podía escribir un método que tuviera un solo argumento que pudiera atender un tipo de valor y un tipo de referencia. Eso es una violación del polimorfismo. Así que se adoptó el boxeo 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 como los tipos de valor no comparten un ancestro de tipo común, tendríamos 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 Boxing Day.


En general, normalmente querrá evitar encasillar sus tipos de valor.

Sin embargo, hay casos raros donde 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 objeto System.Object, que causa el boxeo / desempaquetado.

Todavía hay casos para que esto sea útil en .NET 2.0+. En cualquier momento que desee aprovechar el hecho de que todos los tipos, incluidos los tipos de valor, pueden tratarse como un objeto directamente, es posible que tenga que usar boxeo / desempaquetado. Esto puede ser útil a veces, ya que le permite guardar cualquier tipo en una colección (utilizando 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 tipos. Sin embargo, el único caso en el que se produce el boxeo con frecuencia es cuando utiliza Reflection: muchas de las llamadas en reflexión requerirán boxing / unboxing cuando se trabaja con tipos de valor, ya que el tipo no se conoce de antemano.


La mejor manera de entender esto es mirar los lenguajes de programación de nivel inferior en los que C # se basa.

En los lenguajes de nivel más bajo, como C, todas las variables van en un solo lugar: la pila. Cada vez que se declara una variable se 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. La pila es simple y rápida. A medida que se agregan variables, solo van una encima de la otra, por lo que la primera que declara se queda en, digamos, 0x00, la siguiente en 0x01, la siguiente en 0x02 en la RAM, etc. Además, las variables a menudo se dirigen previamente en compilación tiempo, por lo que su dirección es conocida incluso antes de ejecutar el programa.

En el siguiente nivel, como C ++, se introduce una segunda estructura de memoria llamada Heap. Aún 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 montón es una especie de desorden y algo costoso de mantener, porque a diferencia de las variables de la pila, no se acumulan linealmente hacia arriba y hacia abajo cuando 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 las pérdidas de memoria, el exceso de búfer y la frustración. C # al rescate.

En un nivel superior, C #, no necesita pensar en los punteros: el marco .Net (escrito en C ++) los considera por usted y 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 esas cosas que crean una instancia de una Clase van en el costoso Montón gestionado por la memoria, mientras que los Tipos de valor van en la misma pila que tenía en la C de bajo nivel: súper rápido.

En aras de mantener la interacción entre estos 2 conceptos de memoria (y estrategias para el almacenamiento) fundamentalmente diferentes, simples desde la perspectiva de un codificador, los tipos de valor se pueden incluir 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 ; es más caro, pero es una interacción fluida con el mundo de referencia. Como señalan otras respuestas, esto ocurrirá cuando, por ejemplo, digas:

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

Una fuerte ilustración de la ventaja del boxeo es un cheque para null:

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 la pila que apunta a una copia de nuestro bool b, que se ha copiado al montón. Podemos marcar o para nulo porque el bool ha sido empaquetado 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 aún exigen pasar tipos de valor como objeto (y, por lo tanto, requieren Boxeo), pero en su mayor parte nunca debería necesitar Boxear.

Una lista no exhaustiva de las estructuras históricas de C # que requieren Boxeo, que debe evitar:

  • El sistema de Eventos resulta tener una Condición de Carrera en uso ingenuo, y no es compatible con async. Añadir en el problema del boxeo y probablemente debería evitarse. (Podría reemplazarlo, por ejemplo, con un sistema de eventos asíncronos que use Genéricos).

  • Los viejos modelos Threading y Timer forzaron a una caja en sus parámetros, pero fueron reemplazados por async / await, que son mucho más limpios y eficientes.

  • Las colecciones .Net 1.1 se basaron por completo en el boxeo, porque estaban antes que los genéricos. Estos todavía están pateando alrededor en System.Collections. En cualquier código nuevo, debe utilizar las colecciones de System.Collections.Generic, que además de evitar el boxeo también le proporciona una mayor seguridad de tipos .

Debe evitar declarar o pasar sus tipos de valor como objetos, a menos que tenga que lidiar con los problemas históricos anteriores que obligan al boxeo, y desea evitar el impacto de rendimiento del boxeo más tarde cuando sepa que será empaquetado de todos modos.

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 sugirió originalmente Int32, Bool, etc., boxeo, cuando en realidad son simples alias para tipos de valor. Es decir, .Net tiene tipos como Bool, Int32, String, y C # los alias a bool, int, string, sin ninguna diferencia funcional.