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:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Una buena ilustración de la ventaja del boxeo es un cheque por 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 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
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. 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.