377 votes

Pourquoi avons-nous besoin du boxing et du unboxing en C# ?

Pourquoi avons-nous besoin du boxing et du unboxing en C# ?

Je sais ce qu'est la boxe et l'unboxing, mais je n'arrive pas à en comprendre l'utilité réelle. Pourquoi et où devrais-je l'utiliser ?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing

532voto

Jason Points 125291

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.

0 votes

@Jason Voulez-vous dire que si nous avons des listes primitives, il n'y a pas de raison d'utiliser un boxing/unboxing ?

0 votes

Je ne suis pas sûr de ce que vous voulez dire par "liste primitive".

6 votes

Pourriez-vous s'il vous plaît parler de l'impact sur les performances de boxing y unboxing ?

70voto

Will Points 76760

Dans le cadre .NET, il existe deux types de types : les types de valeurs et les types de références. Cette situation est relativement courante dans les langages OO.

L'une des caractéristiques importantes des langages orientés objet est la possibilité de traiter les instances d'une manière agnostique. C'est ce que l'on appelle polymorphisme . Puisque nous voulons tirer parti du polymorphisme, mais que nous avons deux espèces différentes de types, il doit y avoir un moyen de les réunir pour pouvoir traiter l'une ou l'autre de la même manière.

À l'époque (1.0 de Microsoft.NET), il n'y avait pas ce nouveau charivari de génériques. Vous ne pouviez pas écrire une méthode avec un seul argument qui pouvait 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 un moyen de contraindre un type de valeur dans un objet.

Si cela n'était pas possible, le framework serait jonché de méthodes et de classes dont le seul but était d'accepter l'autre espèce de type. De plus, comme les types de valeurs ne partagent pas vraiment un ancêtre commun, il faudrait avoir une surcharge de méthode différente pour chaque type de valeur (bit, byte, int16, int32, etc etc etc).

La boxe a empêché que cela ne se produise. Et c'est pourquoi les Britanniques célèbrent le Boxing Day.

1 votes

Avant les génériques, l'auto-boxing était nécessaire pour faire beaucoup de choses ; étant donné l'existence des génériques, cependant, s'il n'y avait pas la nécessité de maintenir la compatibilité avec l'ancien code, je pense que .net serait mieux sans conversions de boxing implicites. La conversion d'un type de valeur comme List<string>.Enumerator a IEnumerator<string> donne un objet qui se comporte en grande partie comme un type de classe, mais avec une rupture Equals méthode. Une meilleure façon de lancer List<string>.Enumerator a IEnumerator<string> serait d'appeler un opérateur de conversion personnalisé, mais l'existence d'une conversion implicite empêche cela.

27voto

Ray Points 12711

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 lorsque cela est nécessaire. Par exemple, si vous utilisez une liste matricielle (ArrayList) pour contenir une liste d'entiers, les entiers sont mis en boîte pour tenir dans les emplacements de type objet de la liste matricielle.

En utilisant les collections génériques maintenant, cela disparaît presque complètement. Si vous créez un List<int> il n'y a pas de boxe faite - le List<int> peut contenir les entiers directement.

0 votes

Vous avez toujours besoin d'une boîte pour des choses comme le formatage des chaînes composites. Vous ne le voyez peut-être pas aussi souvent lorsque vous utilisez des génériques, mais c'est toujours le cas.

1 votes

C'est vrai - cela se produit tout le temps dans ADO.NET aussi - les valeurs des paramètres sql sont toutes des "objets", quel que soit le type de données réel.

15voto

STW Points 15326

Le boxing et le unboxing sont spécifiquement utilisés pour traiter les objets de type valeur comme des objets de type référence, en déplaçant leur valeur réelle vers le tas géré et en accédant à leur valeur par référence.

Sans boxing et unboxing, vous ne pourriez jamais transmettre des types de valeurs par référence, ce qui signifie que vous ne pourriez pas transmettre des types de valeurs en tant qu'instances d'Object.

0 votes

Toujours une bonne réponse après presque 10 ans monsieur +1

2 votes

Le passage par référence de types numériques existe dans les langages sans boxe, et d'autres langages traitent les types de valeur comme des instances d'objet sans boxe et déplacent la valeur vers le tas (par exemple, les implémentations de langages dynamiques où les pointeurs sont alignés sur des limites de 4 octets utilisent les quatre bits inférieurs des références pour indiquer que la valeur est un entier ou un symbole plutôt qu'un objet complet ; ces types de valeur sont immuables et de la même taille qu'un pointeur).

8voto

BFree Points 46421

Le dernier endroit où j'ai dû débloquer quelque chose, c'est lorsque j'ai écrit un code qui récupérait des données d'une base de données (je n'utilisais pas l'option LINQ to SQL , tout simplement ADO.NET ):

int myIntValue = (int)reader["MyIntValue"];

En gros, si vous travaillez avec des API plus anciennes, avant les génériques, vous rencontrerez le boxing. En dehors de cela, ce n'est pas très courant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X