web-dev-qa-db-fra.com

Pourquoi avons-nous besoin de boxe et de déballage en C #?

Pourquoi avons-nous besoin de boxe et de déballage en C #?

Je sais ce que sont la boxe et le déballage, mais je ne peux pas en comprendre l’utilisation réelle. Pourquoi et où devrais-je l'utiliser?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing
303
Vaibhav Jain

Pourquoi

Pour avoir un système de types unifié et permettre aux types de valeur 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, un int n'est qu'un seau de trente-deux bits, ce qui 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 à quelque chose quelque part (après tout, il ne s'agit que d'un nombre). Voici ce que vous faites: vous créez un nouveau object pouvant 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’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 de méthode commune pour "représenter" les deux), vous n’avez pas besoin de boxe. Si vous ne voulez pas que int représente leur valeur sous-jacente (c’est-à-dire que les types de référence int soient eux aussi référencés et ne stockent qu’une référence à leur valeur sous-jacente), vous n’avez pas besoin de boxer.

où devrais-je l'utiliser?.

Par exemple, l'ancien type de collection ArrayList ne mange que objects. Autrement dit, il ne stocke que les 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, tu peux.

À l’époque des médicaments génériques, vous n’avez pas vraiment besoin de cela et vous pouvez généralement vous laisser aller sans penser à la question. 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;

Premièrement, nous devons déballer explicitement la double ((double)o), puis la 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);

Réfléchissez-y une seconde avant de passer à la phrase suivante.

Si vous avez dit True et False super! Attends quoi? En effet, == sur les types de référence utilise une égalité de référence qui vérifie si les références sont égales, et non si les valeurs sous-jacentes sont égales. C'est 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, affichera 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 en train d'être encadrée, expliquant la différence de comportement.

457
jason

Dans le framework .NET, il existe deux espèces de types: les types valeur et les types référence. Ceci est relativement courant dans OO langues.

L'une des caractéristiques importantes des langages orientés objet est la capacité de gérer des instances d'une manière indépendante du type. Ceci est appelé polymorphisme . Puisque nous voulons tirer parti du polymorphisme, mais que nous avons deux espèces différentes, il doit y avoir un moyen de les réunir afin de pouvoir traiter l'une ou l'autre de la même manière.

À l'époque jadis (1.0 de Microsoft.NET), il n'existait pas de générique nouveau générique. 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 à 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. De plus, étant donné que les types de valeur ne partagent pas vraiment un ancêtre de type commun, vous devez appliquer une surcharge de méthode différente pour chaque type de valeur (bit, octet, int16, int32, etc., etc.).

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

51
Will

La meilleure façon de comprendre cela consiste à 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: The Stack. Chaque fois que vous déclarez une variable, elle passe sur la pile. Il ne peut s'agir que de valeurs primitives, telles que bool, octet, int 32 bits, uint 32 bits, etc. La pile est à la fois simple et rapide. Au fur et à mesure que les variables sont ajoutées, elles se superposent. Ainsi, le premier que vous déclarez est assis à 0x00, le suivant à 0x01, le suivant à 0x02 dans la RAM, etc. De plus, les variables sont souvent pré-adressées à la compilation. temps, de sorte que leur adresse est connue avant même d’exécuter le programme.

Au niveau supérieur, comme le C++, une deuxième structure de mémoire appelée le tas est introduite. Vous vivez toujours principalement dans la pile, mais des entrées spéciales appelées Des pointeurs peuvent être ajoutés à la pile, qui stocke l'adresse mémoire du premier octet d'un objet. , et cet objet vit dans le tas. Le tas est une sorte de gâchis et un peu coûteux à maintenir, car contrairement aux variables de pile, elles ne s'empilent pas de manière linéaire, puis à la baisse, à l'exécution du programme. Ils peuvent aller et venir sans ordre particulier, et ils peuvent grandir et diminuer.

Traiter avec des pointeurs est difficile. Ils sont la cause des fuites de mémoire, des dépassements de mémoire tampon et de la 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 à des objets, et pour la performance, vous permet de stocker des valeurs plus simples. comme bools, bytes et ints en tant que types de valeur. Sous le capot, les objets et autres éléments qui instancient une classe entrent dans le coûteux segment de mémoire géré, tandis que les types de valeur entrent dans la même pile que celle que vous aviez au plus bas niveau C - super-rapide.

Afin de maintenir l'interaction entre ces 2 concepts de mémoire (et de stratégies de stockage) fondamentalement différents, du point de vue du codeur, les types de valeur peuvent être encadrés à tout moment. La boxe fait que la valeur est copiée de la pile, placée dans un objet et placée sur le tas - plus chère, mais interaction fluide avec le monde de référence. Comme le soulignent d’autres réponses, cela se produira lorsque, par exemple, vous dites:

bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!

Un bon exemple de l’avantage de la boxe est un test de null:

if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false

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é Boxed et mis là.

En général, vous devriez éviter la boxe sauf si vous en avez besoin, par exemple pour passer un int/bool/que ce soit comme objet à un argument. Il existe certaines structures de base dans .Net qui exigent toujours de passer des types de valeur en tant qu'objet (et donc nécessitent la boxe), mais pour la plupart, vous ne devriez jamais avoir besoin de Box.

Une liste non exhaustive de structures C # historiques nécessitant la boxe, que vous devriez éviter:

  • Le système d'événements s'avère avoir une condition de concurrence critique pour une utilisation naïve de celui-ci, et il ne prend pas en charge l'async. Ajoutez dans le problème de boxe et il devrait probablement être évité. (Vous pouvez par exemple le remplacer par un système d’événement asynchrone utilisant des génériques.)

  • Les anciens modèles Threading et Timer forçaient une boîte à définir ses paramètres, mais ont été remplacés par des fonctions asynchrones/wait, beaucoup plus propres et plus efficaces.

  • Les collections .Net 1.1 reposaient entièrement sur la boxe, car elles étaient avant les génériques. Celles-ci sont toujours d'actualité dans System.Collections. Dans tout nouveau code, vous devriez utiliser les collections de System.Collections.Generic, qui en plus d’éviter la boxe, vous aurez également une sécurité de type plus forte .

Vous devez éviter de déclarer ou de transmettre vos types de valeur en tant qu'objets, à moins que vous n'ayez à traiter les problèmes historiques ci-dessus qui forcent la boxe et que vous souhaitiez éviter le coup de performance de la boxe plus tard, sachant que de toute façon, elle sera boxée.

La suggestion de Mikael ci-dessous:

Faire ceci

using System.Collections.Generic;

var employeeCount = 5;
var list = new List<int>(10);

Pas ça

using System.Collections;

Int32 employeeCount = 5;
var list = new ArrayList(10);

Mise à jour

Cette réponse suggérait à l'origine Int32, Bool etc. de provoquer la boxe, alors qu'il s'agit en fait de simples alias pour les types de valeur. C'est-à-dire que .Net a des types tels que Bool, Int32, String et C #, qui les alias: bool, int, string, sans aucune différence fonctionnelle.

32
Chris Moschini

La boxe n’est pas vraiment quelque chose que vous utilisez, c’est quelque chose que le moteur d’exécution utilise pour vous permettre de 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 avez utilisé une liste de tableaux pour contenir une liste d'entiers, les entiers ont été mis en boîte pour tenir dans les emplacements de type d'objet de la liste de tableaux.

En utilisant les collections génériques maintenant, cela disparaît à peu près. Si vous créez un List<int>, il n'y a pas de boxing terminé - le List<int> peut contenir directement les entiers.

19
Ray

Boxing et Unboxing sont spécifiquement utilisés pour traiter les objets de type valeur comme des types 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 les types de valeur par référence; et cela signifie que vous ne pouvez pas passer des types de valeur en tant qu'occurrences de Object.

12
STW

Le dernier endroit où je devais déballer quelque chose était lors de l'écriture d'un code qui récupérait des données d'une base de données (je n'utilisais pas LINQ to SQL , tout simplement vieux ADO.NET ):

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

Fondamentalement, si vous utilisez d'anciennes API avant des génériques, vous rencontrerez la boxe. En dehors de cela, ce n'est pas si commun.

7
BFree

La boxe est obligatoire lorsque nous avons une fonction qui a besoin d'objet en tant que paramètre, mais que différents types de valeur doivent être passés. Dans ce cas, nous devons d'abord convertir les types de valeur 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:

class Program
    {
        static void Main(string[] args)
        {
            int x = 4;
            test(x);
        }

        static void test(object o)
        {
            Console.WriteLine(o.ToString());
        }
    }

Cela fonctionne très bien, je n'ai pas utilisé la boxe/unboxing. (À moins que le compilateur ne le fasse en coulisses?)

4
Manoj

Dans .net, chaque instance de Object, ou tout type qui en dérive, comprend une structure de données contenant des informations sur son type. Les types de valeurs "réelles" dans .net ne contiennent aucune information de ce type. 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 d'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 en copiant les champs d'une instance de type valeur. Unboxing copie les champs d'une instance du type de classe dans une instance du type valeur. Tous les types de classe créés à partir de types valeur sont dérivés de la classe nommée ironiquement ValueType (qui, malgré son nom, est en réalité un type de référence).

1
supercat

Lorsqu'une méthode prend uniquement un type de référence en tant que paramètre (par exemple, une méthode générique contrainte à être une classe via la contrainte new], vous ne pourrez pas lui passer un type de référence et vous devrez l'encadrer.

Ceci est également vrai pour toutes les méthodes qui prennent object en tant que paramètre - ce qui aura sera un type de référence.

0
Oded

En général, vous voudrez généralement éviter de mettre en boîte vos types de valeur.

Cependant, il y a de rares occasions où cela est utile. Si vous devez cibler le framework 1.1, par exemple, vous n’avez pas accès aux collections génériques. Toute utilisation des collections dans .NET 1.1 nécessiterait de traiter votre type de valeur en tant que System.Object, ce qui entraîne la boxing/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 en tant qu'objet, vous devrez peut-être utiliser boxing/unboxing. Cela peut être utile parfois, car cela vous permet de sauvegarder n'importe quel type dans une collection (en utilisant object au lieu de T dans une collection générique), mais en général, il est préférable d'é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 boxing/unboxing lors de l'utilisation de types de valeur, car le type n'est pas connu à l'avance.

0
Hunain