Question

La façon de créer .NET 1.0 collection de nombres entiers (par exemple) était la suivante:

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */

La peine d'utiliser est le manque de sécurité et de performance de type à cause de la boxe et unboxing.

2,0 .NET moyen est d'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, copiez la pile entière allouée au nouvel objet et vice-versa pour unboxing.

Comment l'utilisation des médicaments génériques ne surmonte cela? Est-ce que la pile allouée reste entier sur la pile et pointé du tas (je suppose que ce n'est pas le cas à cause de ce qui se passera quand il sortira de la portée)? Il semble qu'il y ait encore un besoin de le copier ailleurs de la pile.

Qu'est-ce qui se passe réellement?

Était-ce utile?

La solution

En ce qui concerne les collections, les génériques permettent d'éviter la boxe / unboxing en utilisant des tableaux de T[] réels en interne. List<T> par exemple utilise un tableau de T[] pour stocker son contenu.

array , bien sûr, est un type de référence et est donc (dans la version actuelle du CLR, patata) stockés sur le tas. Mais comme il est un T[] et non un object[], les éléments du tableau peuvent être stockés « directement »: à savoir, ils sont toujours sur le tas, mais ils sont sur le tas dans le tableau au lieu de être emballé et ayant le tableau contient des références aux cases.

Donc, pour un List<int>, par exemple, ce que vous avez dans le tableau serait « look » comme ceci:

[ 1 2 3 ]

Comparez cela à une ArrayList, qui utilise un object[] et serait donc « look » quelque chose comme ceci:

[ *a *b *c ]

... où *a, etc. sont des références à des objets (entiers encadré):

*a -> 1
*b -> 2
*c -> 3

Excusez ces illustrations bruts; nous espérons que vous savez ce que je veux dire.

Autres conseils

Votre confusion est un malentendu que la relation entre la pile, le tas et les variables. Voici la bonne façon de penser.

  • Une variable est un emplacement de stockage qui a un type.
  • La durée de vie d'une variable peut être soit court ou long. Par « court », nous entendons « jusqu'à ce que le retour de la fonction en cours ou lance » et par « long » nous voulons dire « peut-être plus que cela. »
  • Si le type d'une variable est un type de référence alors le contenu de la variable est une référence à un emplacement de stockage de longue durée. Si le type d'une variable est un type de valeur alors le contenu de la variable est une valeur.

Comme un détail de mise en œuvre, un emplacement de stockage qui est garanti pour être de courte durée peuvent être alloués sur la pile. Un emplacement de stockage qui pourrait être de longue durée est allouée sur le tas. Notez que cela ne dit rien au sujet de « types de valeurs sont toujours allouées sur la pile. » Les types de valeur sont pas toujours alloué sur la pile:

int[] x = new int[10];
x[1] = 123;

x[1] est un emplacement de stockage. Elle est longue durée; il pourrait vivre plus longtemps que cette méthode. Par conséquent, il doit être sur le tas. Le fait qu 'elle contient un int est sans importance.

Vous dites bien pourquoi un int est cher boxed:

  

Le prix de la boxe est la nécessité de créer un objet sur le tas, copiez la pile entière allouée au nouvel objet et vice-versa pour unboxing.

Où que vous alliez mal est-à-dire « la pile entière alloué ». Peu importe où l'entier a été attribué. Ce qui importe est que son stockage contient 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 faire la copie; c'est le seul coût qui est pertinent.

Alors, pourquoi n'est pas une variable générique coûteuse? Si vous avez une variable de type T et T est conçu pour être int, alors vous avez une variable de type int, période. Une variable de type int est un emplacement de stockage, et il contient un int. Si cet emplacement de stockage est sur la pile ou le tas est totalement indifférent . Ce qui est pertinent 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 ne devez pas prendre sur les coûts de la boxe et unboxing:. Allouer un nouveau stockage sur le tas et la copie int au nouveau stockage

Est-ce clair maintenant?

Génériques permet au lieu de int[] efficacement à taper le tableau interne de la liste object[], ce qui nécessiterait la boxe.

Voici ce qui se passe sans génériques:

  1. Vous appelez Add(1).
  2. Le 1 entier est emballé dans un objet, ce qui nécessite un nouvel objet à construire sur le tas.
  3. Cet objet est passé à ArrayList.Add().
  4. L'objet boxed est bourré dans un object[].

Il y a trois niveaux d'indirection ici. ArrayList -> object[] -> object -> int

Avec génériques:

  1. Vous appelez Add(1).
  2. int 1 est passé à List<int>.Add().
  3. int est bourré dans un int[].

Donc, il n'y a que deux niveaux d'indirection. List<int> -> int[] -> int

Quelques autres différences:

  • La méthode non générique, il faudra un total de 8 ou 12 octets (un pointeur, une int) pour stocker la valeur, dans une répartition 4/8 et 4 dans l'autre. Et ce sera probablement plus en raison de l'alignement et le rembourrage. La méthode générique nécessitera seulement 4 octets d'espace dans le tableau.
  • La méthode non générique nécessite l'attribution d'un int boxed; la méthode générique ne fonctionne pas. Ceci est plus rapide et réduit le taux de désabonnement de GC.
  • La méthode non générique nécessite moulages pour extraire des valeurs. Ce n'est pas Typesafe et il est un peu plus lent.

Un ArrayList ne gère que le object de type afin d'utiliser cette classe nécessite coulée vers et à partir object. Dans le cas des types de valeur, cette coulée implique la boxe et unboxing.

Lorsque vous utilisez une liste générique les sorties du compilateur code spécialisé pour ce type de valeur afin que les valeurs réelles sont stockés dans la liste plutôt qu'une référence à des objets qui contiennent les valeurs. Par conséquent, aucune boxe est nécessaire.

  

Le prix de la boxe (à ma connaissance) est la nécessité de créer un objet sur le tas, copiez la pile entière allouée au nouvel objet et vice-versa pour unboxing.

Je pense que vous assumez que les types de valeur sont toujours instanciés sur la pile. Ce n'est pas le cas - ils peuvent être créés soit sur le tas, sur la pile ou dans les registres. Pour plus d'informations sur cette s'il vous plaît voir l'article d'Eric Lippert: La vérité sur la valeur Types .

1 .NET, lorsque la méthode est appelée Add:

  1. L'espace est alloué sur le tas; est fait une nouvelle référence
  2. Le contenu de la variable i est copié dans la référence
  3. Une copie de la référence est mis à la fin de la liste

Dans .NET 2:

  1. Une copie de la i variable est transmise à la méthode de Add
  2. Une copie de cette copie est mis à la fin de la liste

Oui, la variable i est toujours copié (, il est après tout un type de valeur, et les types de valeur sont toujours copiés - même si elles ne sont que des paramètres de la méthode). Mais il n'y a pas de copie redondante fait sur le tas.

Pourquoi pensez-vous en termes de WHERE les valeurs \ objets sont stockés? En C # types de valeur peuvent être stockés sur la pile ainsi que tas en fonction de ce que le CLR choisit.

Si génériques font une différence WHAT est stockée dans la collection. En cas de ArrayList la collection contient des références à des objets en boîte alors que le List<int> contient int valeurs elles-mêmes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top