Pourquoi
Pour avoir un système de types unifié et permettre aux types de valeurs d'avoir une représentation complètement différente de leurs données sous-jacentes par rapport à la façon dont les types de référence représentent leurs données sous-jacentes (par exemple, an int
est juste un ensemble de trente-deux bits qui est complètement différent d'une référence type).
Pensez-y comme ça. Vous avez une variable o
de type object
. Et maintenant vous en avez un int
et vous voulez le mettre o
. o
est une référence à quelque chose quelque part, et ce int
n'est absolument pas une référence à quelque chose quelque part (après tout, c'est juste un nombre). Donc, ce que vous faites est le suivant: vous créez un nouveau object
qui peut stocker le int
, puis vous affectez une référence à cet objet o
. Nous appelons ce processus «boxe».
Donc, 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 valeurs ont des représentations très différentes et que vous ne voulez pas une façon commune de "représenter" les deux), alors vous n'avez pas besoin de boxe. Si vous ne vous souciez pas d'avoir int
représenté leur valeur sous-jacente (c.-à-d., Avoir à la place des int
types de référence aussi et simplement stocker une référence à leur valeur sous-jacente), 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. Autrement dit, il ne stocke que des références à quelque chose qui vit quelque part. Sans boxe, vous ne pouvez pas mettre un int
dans une telle collection. Mais avec la boxe, c'est possible.
Maintenant, à l'époque des génériques, vous n'avez pas vraiment besoin de cela et pouvez généralement aller gaiement sans réfléchir au problème. Mais il y a quelques mises en garde à prendre en compte:
C'est correct:
double e = 2.718281828459045;
int ee = (int)e;
Ce n'est pas:
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;
Nous devons d'abord décompresser explicitement le double
( (double)o
), puis le convertir 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);
Pensez-y une seconde avant de passer à la phrase suivante.
Si vous avez dit True
et False
super! Attends quoi? C'est parce que ==
sur les types de référence utilise l'égalité de référence qui vérifie si les références sont égales, pas si les valeurs sous-jacentes sont égales. C'est une erreur dangereusement facile à faire. Peut-être encore plus subtil
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 sera ensuite, heureusement, imprimé 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);
Quelle est la sortie? Ça dépend! Si Point
est un struct
alors la sortie est 1
mais si Point
est un class
alors la sortie est 2
! Une conversion de boxe crée une copie de la valeur encadrée expliquant la différence de comportement.
boxing
etunboxing
?Dans le framework .NET, il existe deux types de types: les types de valeur et les types de référence. Ceci est relativement courant dans les langues OO.
L'une des caractéristiques importantes des langages orientés objet est la capacité de gérer les instances de manière agnostique. C'est ce qu'on appelle le polymorphisme . Puisque nous voulons profiter du polymorphisme, mais que nous avons deux espèces différentes de types, il doit y avoir un moyen de les réunir afin que nous puissions gérer l'un ou l'autre de la même manière.
Maintenant, dans les temps anciens (1.0 de Microsoft.NET), il n'y avait pas ce hullabaloo générique nouveau. Vous ne pouviez pas écrire une méthode ayant un seul argument pouvant servir un type de valeur et un type de référence. C'est une violation du polymorphisme. La boxe a donc été adoptée comme moyen de contraindre un type de valeur en un objet.
Si cela n'était pas possible, le cadre serait jonché de méthodes et de classes dont le seul but était d'accepter les autres espèces de type. Non seulement cela, mais comme les types de valeurs ne partagent pas vraiment un ancêtre de type commun, vous devez avoir une surcharge de méthode différente pour chaque type de valeur (bit, octet, int16, int32, etc etc etc).
La boxe a empêché cela de se produire. Et c'est pourquoi les Britanniques célèbrent le lendemain de Noël.
la source
List<string>.Enumerator
pourIEnumerator<string>
produire un objet qui se comporte principalement comme un type de classe, mais avec uneEquals
méthode cassée . Une meilleure façon de convertirList<string>.Enumerator
enIEnumerator<string>
serait d'appeler un opérateur de conversion personnalisé, mais l'existence d'une conversion implicite empêche cela.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:
Une illustration forte de l'avantage de la boxe est une vérification de null:
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
Pas ça
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.
la source
La boxe n'est pas vraiment quelque chose que vous utilisez - c'est quelque chose que le runtime utilise pour que vous puissiez gérer les types de référence et de valeur de la même manière si nécessaire. Par exemple, si vous avez utilisé une ArrayList pour contenir une liste d'entiers, les entiers ont été encadrés pour tenir dans les emplacements de type objet de l'ArrayList.
En utilisant des collections génériques maintenant, cela disparaît à peu près. Si vous créez un
List<int>
, aucune boxe n'est effectuée - leList<int>
peut contenir les entiers directement.la source
Boxing et Unboxing sont spécifiquement utilisés pour traiter les objets de type valeur comme type de référence; déplacer leur valeur réelle vers le tas géré et accéder à leur valeur par référence.
Sans boxing et unboxing, vous ne pourriez jamais transmettre des types de valeur par référence; et cela signifie que vous ne pouvez pas passer des types de valeurs comme instances d'Object.
la source
Le dernier endroit où j'ai dû déballer quelque chose était lors de l'écriture de code qui récupérait certaines données d'une base de données (je n'utilisais pas LINQ to SQL , tout simplement l'ancien ADO.NET ):
Fondamentalement, si vous travaillez avec des API plus anciennes avant les génériques, vous rencontrerez de la boxe. A part ça, ce n'est pas si courant.
la source
La boxe est requise, lorsque nous avons une fonction qui a besoin d'un objet en tant que paramètre, mais que nous avons différents types de valeurs qui doivent être transmis, dans ce cas, nous devons d'abord convertir les types de valeurs en types de données d'objet avant de les transmettre à la fonction.
Je ne pense pas que ce soit vrai, essayez plutôt ceci:
Cela fonctionne très bien, je n'ai pas utilisé de boxe / unboxing. (À moins que le compilateur ne fasse cela en coulisses?)
la source
Dans .net, chaque instance d'Object, ou tout type dérivé de celui-ci, comprend une structure de données qui contient des informations sur son type. Les types de valeur "réels" dans .net ne contiennent pas de telles informations. Pour permettre aux données des types de valeur d'être manipulées par des routines qui s'attendent à recevoir des types dérivés de l'objet, le système définit automatiquement pour chaque type de valeur un type de classe correspondant avec les mêmes membres et champs. Boxing crée une nouvelle instance de ce type de classe, copiant les champs d'une instance de type valeur. Unboxing copie les champs d'une instance du type classe vers une instance du type valeur. Tous les types de classe qui sont créés à partir des types de valeur sont dérivés de la classe ironiquement nommée ValueType (qui, malgré son nom, est en fait un type de référence).
la source
Lorsqu'une méthode ne prend qu'un type de référence comme paramètre (disons une méthode générique contrainte d'être une classe via la
new
contrainte), vous ne pourrez pas lui passer un type de référence et vous devrez le mettre en boîte.Cela est également vrai pour toutes les méthodes qui prennent
object
comme paramètre - cela a être un type de référence.la source
En général, vous voudrez généralement éviter de mettre en boîte vos types de valeur.
Cependant, il existe de rares cas où cela est utile. Si vous devez cibler le framework 1.1, par exemple, vous n'aurez pas accès aux collections génériques. Toute utilisation des collections dans .NET 1.1 nécessiterait de traiter votre type de valeur comme un System.Object, ce qui provoque la boxe / unboxing.
Il existe encore des cas pour que cela soit utile dans .NET 2.0+. Chaque fois que vous souhaitez tirer parti du fait que tous les types, y compris les types de valeur, peuvent être traités directement comme un objet, vous devrez peut-être utiliser la boxe / unboxing. Cela peut être pratique parfois, car il vous permet d'enregistrer n'importe quel type dans une collection (en utilisant un objet au lieu de T dans une collection générique), mais en général, il vaut mieux éviter cela, car vous perdez la sécurité du type. Le seul cas où la boxe se produit fréquemment, cependant, est lorsque vous utilisez la réflexion - de nombreux appels en réflexion nécessiteront une boxe / unboxing lorsque vous travaillez avec des types de valeur, car le type n'est pas connu à l'avance.
la source
La boxe est la conversion d'une valeur en un type de référence avec les données à un certain décalage dans un objet sur le tas.
Quant à ce que fait réellement la boxe. Voici quelques exemples
Mono C ++
Unboxing en Mono est un processus de conversion d'un pointeur à un décalage de 2 gpointers dans l'objet (par exemple 16 octets). A
gpointer
est unvoid*
. Cela a du sens lorsque l'on regarde la définition deMonoObject
car il ne s'agit clairement que d'un en-tête pour les données.C ++
Pour encadrer une valeur en C ++, vous pouvez faire quelque chose comme:
la source