La façon .NET 1.0 de créer une collection d'entiers (par exemple) était:
ArrayList list = new ArrayList();
list.Add(i); /* boxing */
int j = (int)list[0]; /* unboxing */
La pénalité d'utilisation est le manque de sécurité et de performances du type dû à la boxe et au déballage.
La manière .NET 2.0 consiste à utiliser des génériques:
List<int> list = new List<int>();
list.Add(i);
int j = list[0];
Le prix de la boxe (à ma connaissance) est la nécessité de créer un objet sur le tas, de copier l'entier alloué de la pile vers le nouvel objet et vice-versa pour le déballage.
Comment l'utilisation des génériques résout-elle cela? Est-ce que l'entier alloué par pile reste sur la pile et est pointé du tas (je suppose que ce n'est pas le cas à cause de ce qui se passera quand il sortira du champ d'application)? Il semble qu'il soit encore nécessaire de le copier ailleurs dans la pile.
Qu'est-ce qui se passe réellement?
En ce qui concerne les collections, les génériques permettent d'éviter la boxe/unboxing en utilisant des tableaux T[]
Réels en interne. List<T>
Utilise par exemple un tableau T[]
Pour stocker son contenu.
Le tablea, bien sûr, est un type de référence et est donc (dans la version actuelle du CLR, yada yada) stocké sur le tas. Mais comme c'est un T[]
Et non un object[]
, Les éléments du tableau peuvent être stockés "directement": c'est-à-dire qu'ils sont toujours sur le tas, mais ils sont sur le tas - dans le tablea au lieu d'être encadré et que le tableau contienne des références aux boîtes.
Ainsi, pour un List<int>
, Par exemple, ce que vous auriez dans le tableau "ressemblerait" à ceci:
[1 2 3]
Comparez cela à un ArrayList
, qui utilise un object[]
Et qui "ressemblerait" donc à quelque chose comme ceci:
[* a * b * c]
... où *a
, etc. sont des références à des objets (entiers encadrés):
* a -> 1 * b -> 2 * c -> 3
Excusez ces illustrations grossières; j'espère que vous savez ce que je veux dire.
Votre confusion est le résultat d'une mauvaise compréhension de la relation entre la pile, le tas et les variables. Voici la bonne façon d'y penser.
En tant que détail d'implémentation, un emplacement de stockage dont la durée de vie est garantie peut être alloué sur la pile. Un emplacement de stockage qui peut être de longue durée est alloué sur le tas. Notez que cela ne dit rien sur "les types de valeurs sont toujours alloués sur la pile". Les types de valeurs ne sont pas toujours alloués sur la pile:
int[] x = new int[10];
x[1] = 123;
x[1]
est un emplacement de stockage. C'est une vie longue; il pourrait vivre plus longtemps que cette méthode. Il doit donc être sur le tas. Le fait qu'il contienne un int est sans importance.
Vous dites correctement pourquoi un int en boîte coûte cher:
Le prix de la boxe est la nécessité de créer un objet sur le tas, de copier l'entier alloué de la pile vers le nouvel objet et vice-versa pour le déballage.
Là où vous vous trompez, c'est "l'entier alloué par pile". Peu importe où l'entier a été alloué. Ce qui importe, c'est que son stockage contenait l'entier , au lieu de contenir une référence à un emplacement de tas . Le prix est la nécessité de créer l'objet et de faire la copie; c'est le seul coût qui soit pertinent.
Alors pourquoi une variable générique n'est-elle pas coûteuse? Si vous avez une variable de type T et que T est construit pour être int, alors vous avez une variable de type int, point. Une variable de type int est un emplacement de stockage et contient un int. Que cet emplacement de stockage soit sur la pile ou que le tas soit complètement hors de propos. Ce qui est pertinent, c'est que l'emplacement de stockage contient un int , au lieu de contenir une référence à quelque chose sur le tas . Étant donné que l'emplacement de stockage contient un int, vous n'avez pas à assumer les coûts de boxing et unboxing: allouer un nouveau stockage sur le tas et copier l'int dans le nouveau stockage.
Est-ce maintenant clair?
Un ArrayList ne gère que le type object
, donc pour utiliser cette classe, il faut effectuer un cast vers et depuis object
. Dans le cas des types de valeur, ce casting implique la boxe et le déballage.
Lorsque vous utilisez une liste générique, le compilateur génère un code spécialisé pour ce type de valeur afin que les valeurs réelles soient stockées dans la liste plutôt qu'une référence aux objets qui contiennent les valeurs. Aucune boxe n'est donc requise.
Le prix de la boxe (à ma connaissance) est la nécessité de créer un objet sur le tas, de copier l'entier alloué de la pile vers le nouvel objet et vice-versa pour le déballage.
Je pense que vous supposez que les types de valeurs sont toujours instanciés sur la pile. Ce n'est pas le cas - ils peuvent être créés sur le tas, sur la pile ou dans des registres. Pour plus d'informations à ce sujet, veuillez consulter l'article d'Eric Lippert: The Truth About Value Types .
Les génériques permettent de taper le tableau interne de la liste int[]
Au lieu de object[]
, Ce qui nécessiterait de la boxe.
Voici ce qui se passe sans génériques:
Add(1)
.1
Est encadré dans un objet, ce qui nécessite qu'un nouvel objet soit construit sur le tas.ArrayList.Add()
.object[]
.Il existe trois niveaux d'indirection ici: ArrayList
-> object[]
-> object
-> int
.
Avec des génériques:
Add(1)
.List<int>.Add()
.int[]
.Il n'y a donc que deux niveaux d'indirection: List<int>
-> int[]
-> int
.
Quelques autres différences:
Dans .NET 1, lorsque la méthode Add
est appelée:
i
est copié dans la référenceDans .NET 2:
i
est passée à la méthode Add
Oui, la variable i
est toujours copiée (après tout, c'est un type de valeur, et les types de valeur sont toujours copiés - même si ce ne sont que des paramètres de méthode). Mais il n'y a pas de copie redondante faite sur le tas.
Pourquoi pensez-vous en termes de WHERE
les valeurs\objets sont stockés? En C #, les types de valeurs peuvent être stockés sur la pile ainsi que sur le tas en fonction de ce que le CLR choisit.
Lorsque les génériques font la différence, WHAT
est stocké dans la collection. Dans le cas de ArrayList
, la collection contient des références à des objets encadrés alors que List<int>
contient les valeurs int elles-mêmes.