La meilleure façon de comprendre cela est d'examiner les langages de programmation de niveau inférieur sur lesquels C # s'appuie.
Dans les langages de niveau le plus bas comme C, toutes les variables vont au même endroit: la pile. Chaque fois que vous déclarez une variable, elle va sur la pile. Il ne peut s'agir que de valeurs primitives, comme un booléen, un octet, un entier 32 bits, un uint 32 bits, etc. La pile est à la fois simple et rapide. Au fur et à mesure que des variables sont ajoutées, elles vont juste l'une au-dessus de l'autre, donc la première que vous déclarez se trouve à dire, 0x00, la suivante à 0x01, la suivante à 0x02 en RAM, etc. En outre, les variables sont souvent pré-adressées lors de la compilation- temps, donc leur adresse est connue avant même d'exécuter le programme.
Au niveau suivant, comme C ++, une deuxième structure de mémoire appelée Heap est introduite. Vous vivez toujours principalement dans la pile, mais des entrées spéciales appelées pointeurs peuvent être ajoutées à la pile, qui stockent l'adresse mémoire pour le premier octet d'un objet, et cet objet vit dans le tas. Le tas est une sorte de gâchis et quelque peu coûteux à entretenir, car contrairement aux variables de pile, elles ne s'empilent pas de façon linéaire vers le haut et vers le bas pendant l'exécution d'un programme. Ils peuvent aller et venir sans séquence particulière, et ils peuvent grandir et rétrécir.
Il est difficile de gérer les pointeurs. Ils sont à l'origine de fuites de mémoire, de dépassements de mémoire tampon et de frustration. C # à la rescousse.
À un niveau supérieur, C #, vous n'avez pas besoin de penser aux pointeurs - le framework .Net (écrit en C ++) y pense pour vous et vous les présente comme des références aux objets, et pour les performances, vous permet de stocker des valeurs plus simples comme bools, octets et ints comme types de valeur. Sous le capot, les objets et les éléments qui instancient une classe se trouvent sur le tas coûteux géré par la mémoire, tandis que les types de valeur vont dans la même pile que vous aviez en C de bas niveau - super rapide.
Par souci de simplifier l'interaction entre ces 2 concepts fondamentalement différents de la mémoire (et stratégies de stockage) du point de vue du codeur, les types de valeur peuvent être encadrés à tout moment. Avec la boxe, la valeur est copiée de la pile, placée dans un objet et placée sur le tas - interaction plus coûteuse mais fluide avec le monde de référence. Comme d'autres réponses le soulignent, cela se produit lorsque vous dites par exemple:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Une illustration forte de l'avantage de la boxe est une vérification de null:
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
Notre objet o est techniquement une adresse dans la pile qui pointe vers une copie de notre bool b, qui a été copiée dans le tas. Nous pouvons vérifier o pour null car le bool a été mis en boîte et mis là.
En général, vous devez éviter la boxe sauf si vous en avez besoin, par exemple pour passer un int / bool / que ce soit en tant qu'objet à un argument. Il existe certaines structures de base dans .Net qui exigent toujours le passage de types de valeur en tant qu'objet (et nécessitent donc la boxe), mais pour la plupart, vous ne devriez jamais avoir besoin de Boxer.
Une liste non exhaustive des structures C # historiques qui nécessitent la boxe, que vous devez éviter:
Le système d'événement se révèle avoir une condition de race dans son utilisation naïve, et il ne prend pas en charge l'async. Ajoutez le problème de boxe et cela devrait probablement être évité. (Vous pouvez le remplacer par exemple par un système d'événement asynchrone qui utilise des génériques.)
Les anciens modèles Threading et Timer ont forcé une boîte sur leurs paramètres mais ont été remplacés par async / wait qui sont beaucoup plus propres et plus efficaces.
Les collections .Net 1.1 reposaient entièrement sur la boxe, car elles étaient antérieures aux génériques. Ceux-ci sont toujours en marche dans System.Collections. Dans tout nouveau code, vous devez utiliser les collections de System.Collections.Generic, qui en plus d'éviter la boxe, vous offrent également une sécurité de type plus forte .
Vous devez éviter de déclarer ou de passer vos types de valeurs en tant qu'objets, à moins que vous n'ayez à faire face aux problèmes historiques ci-dessus qui forcent la boxe, et que vous souhaitiez éviter les performances de boxe plus tard lorsque vous saurez que cela va être boxé de toute façon.
Selon la suggestion de Mikael ci-dessous:
Faites ceci
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
Pas ça
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
Mettre à jour
Cette réponse a initialement suggéré que Int32, Bool, etc. provoquent la boxe, alors qu'en fait ce sont de simples alias pour les types de valeur. C'est-à-dire que .Net a des types comme Bool, Int32, String et C # qui les alias bool, int, string, sans aucune différence fonctionnelle.