Pourquoi
Pour disposer d'un système de types unifié et permettre aux types de valeur d'avoir une représentation de leurs données sous-jacentes complètement différente de celle des types de référence (par exemple, une int
est juste un panier de trente-deux bits, ce qui est complètement différent d'un type de référence).
Pensez-y comme ça. Vous avez une variable o
de type object
. Et maintenant vous avez un int
et vous voulez le mettre dans o
. o
est une référence à quelque chose quelque part, et le int
n'est absolument pas une référence à un objet quelconque (après tout, ce n'est qu'un nombre). Donc, voici ce que vous faites : vous créez un nouvel objet object
qui peut stocker le int
et ensuite vous assignez une référence à cet objet à o
. Nous appelons ce processus "mise en boîte".
Ainsi, si vous ne vous souciez pas d'avoir un système de types unifié (c'est-à-dire que les types de référence et les types de valeur ont des représentations très différentes et que vous ne voulez pas d'une manière commune de "représenter" les deux), alors vous n'avez pas besoin de la boxe. Si vous ne vous souciez pas d'avoir int
représentent leur valeur sous-jacente (c'est-à-dire qu'ils ont plutôt int
sont également des types de référence et stockent simplement une référence à leur valeur sous-jacente), alors vous n'avez pas besoin de boxe.
où dois-je l'utiliser ?
Par exemple, l'ancien type de collection ArrayList
ne mange que object
s. C'est-à-dire qu'il ne stocke que les références à des choses qui vivent quelque part. Sans boxe, vous ne pouvez pas mettre un int
dans une telle collection. Mais avec la boxe, c'est possible.
Aujourd'hui, à l'époque des génériques, vous n'en avez plus vraiment besoin et vous pouvez généralement vous en passer sans vous soucier de ce problème. Mais il y a quelques mises en garde à prendre en compte :
C'est exact :
double e = 2.718281828459045;
int ee = (int)e;
Ce n'est pas le cas :
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
Au lieu de cela, vous devez faire ceci :
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
Tout d'abord, nous devons débloquer explicitement le double
( (double)o
), puis le transforme en un int
.
Quel est le résultat de ce qui suit :
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
Réfléchissez-y une seconde avant de passer à la phrase suivante.
Si vous avez dit True
y False
Super ! Attends, quoi ? C'est parce que ==
sur les types de référence utilise reference-equality qui vérifie si les références sont égales, et non si les valeurs sous-jacentes sont égales. Il s'agit d'une erreur dangereusement facile à commettre. Peut-être encore plus subtile
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
imprimera également False
!
Mieux vaut dire :
Console.WriteLine(o1.Equals(o2));
qui, heureusement, imprimera ensuite True
.
Une dernière subtilité :
[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);
Quel est le résultat ? Cela dépend ! Si Point
es un struct
alors la sortie est 1
mais si Point
es un class
alors la sortie est 2
! Une conversion en boîte fait une copie de la valeur mise en boîte, ce qui explique la différence de comportement.