Maison > Article > développement back-end > Compilation de connaissances sur la boxe et le déballage C#
1. La boxe et le déballage sont des concepts abstraits
2. La boxe convertit les types de valeur en types de référence
Le déballage convertit les types de référence en types de valeur
À l'aide des fonctions de boxe et de déballage, les types valeur peuvent être liés aux types référence en permettant à n'importe quelle valeur du type valeur d'être convertie vers et depuis le type Objet
Par exemple :
int val = 100; object obj = val; Console.WriteLine (“对象的值 = {0}", obj);
Il s'agit d'un processus de boxe, qui est le processus de conversion des types valeur en types référence
int val = 100; object obj = val; int num = (int) obj; Console.WriteLine ("num: {0}", num);
Il s'agit d'un processus de déballage, qui est le processus de conversion des types valeur en types référence, puis par types référence Le processus de conversion en une valeur type
Remarque : seuls les objets encadrés peuvent être déballés
3. Dans .NET, les types de données sont divisés en types de valeur et en types de référence (non équivalents aux pointeurs), L'allocation de mémoire est donc divisée en deux De différentes manières, l'un est la pile et l'autre est le tas (remarque : il s'agit d'un tas géré)
Les types de valeur ne seront alloués que sur la pile.
Les types de référence allouent de la mémoire et du tas géré.
Le tas géré correspond au garbage collection.
4 : Qu’est-ce que le boxing/unboxing ?
Boxing : utilisé pour stocker les types de valeur dans le tas de garbage collection. La boxe est une conversion implicite d’un type valeur en type objet ou en tout type d’interface implémenté par le type valeur.
Unboxing : une conversion explicite d'un type d'objet en un type valeur ou d'un type d'interface en un type valeur qui implémente l'interface.
5 : Pourquoi un emballage est-il nécessaire ? (Pourquoi convertir les types valeur en types référence ?)
L'un des scénarios les plus courants consiste à appeler une méthode avec un paramètre de type Object. L'Object peut prendre en charge n'importe quel type pour une utilisation universelle. Lorsque vous devez transmettre un type valeur (tel que Int32), la boxe est requise.
Une autre utilisation est un conteneur non générique, également pour garantir l'universalité, le type d'élément est défini comme Objet. Par conséquent, lors de l’ajout de données de type valeur à un conteneur, le boxing est requis.
6 : Opérations internes de boxing/unboxing
Boxing
Alloue une instance d'objet dans le tas pour un type de valeur et copie la valeur dans un nouvel objet. Suivez trois étapes.
Mémoire de tas gérée nouvellement allouée (la taille est la taille de l'instance de type valeur plus un pointeur de table de méthodes et un SyncBlockIndex).
Copiez les champs d'instance du type valeur dans la mémoire nouvellement allouée.
Renvoie l'adresse de l'objet nouvellement alloué dans le tas géré. Cette adresse est une référence à l'objet.
Certaines personnes le comprennent ainsi : si Int32 est encadré, l'adresse renvoyée pointe vers un Int32. Je pense qu'il n'est pas impossible de le comprendre de cette façon, mais cela pose des problèmes. Premièrement, il n'est pas complet et, deuxièmement, pointer vers Int32 ne révèle pas son essence (dans le tas géré).
Unboxing
Vérifie l'instance d'objet pour s'assurer qu'il s'agit d'une valeur encadrée du type de valeur donné. Copie la valeur de l'instance dans une variable de type valeur.
Selon certains livres, le déballage n'obtient que le pointeur vers la partie type valeur de l'objet de référence, et la copie du contenu est le déclencheur de l'instruction d'affectation. Je ne pense pas que cela ait de l'importance. La chose la plus importante est de vérifier la nature de l'instance d'objet. Les types de unboxing et de boxing doivent correspondre. À ce stade, au niveau de la couche IL, je ne vois pas le principe, c'est peut-être qu'une méthode comme GetType est appelée. . Supprimez le type pour la correspondance (car une correspondance stricte est requise).
7 : L'impact du boxing/unboxing sur l'efficacité d'exécution
Évidemment, cela ressort du principe que lors du boxing, un tout nouvel objet de référence est généré, ce qui prendra du temps, c'est-à-dire, ce qui entraîne une efficacité réduite.
Que faire ?
Tout d’abord, la boxe doit être évitée autant que possible.
Par exemple, les deux situations de l'exemple 2 ci-dessus peuvent être évitées. Dans le premier cas, cela peut être évité en surchargeant la fonction. Le deuxième cas peut être évité grâce aux génériques.
Bien sûr, rien n'est absolu. Supposons que le code que vous souhaitez modifier soit un assembly tiers et que vous ne puissiez pas le modifier, vous ne pouvez alors que le mettre en boîte.
Pour l'optimisation du code de boxing/unboxing, puisque le boxing et le unboxing sont implicites en C#, la méthode fondamentale est d'analyser le code, et la manière la plus directe d'analyser est de comprendre la structure principale Voir le code IL décompilé.
Par exemple : il peut y avoir une boxe redondante dans le corps de la boucle, vous pouvez simplement utiliser une boxe avancée pour l'optimisation.
8 : Meilleure compréhension de l'emballage/unboxing
L'emballage/unboxing n'est pas aussi simple et clair que mentionné ci-dessus
Par exemple : lors de l'emballage, changez Pour référencer un objet, il y aura un pointeur de table de méthode supplémentaire. A quoi sert-il ?
Nous pouvons explorer davantage avec des exemples.
Par exemple :
Struct A : ICloneable { public Int32 x; public override String ToString() { return String.Format(”{0}”,x); } public object Clone() { return MemberwiseClone(); } } static void main() { A a; a.x = 100; Console.WriteLine(a.ToString()); Console.WriteLine(a.GetType()); A a2 = (A)a.Clone(); ICloneable c = a2; Ojbect o = c.Clone(); }
a.ToString()。编译器发现A重写了ToString方法,会直接调用ToString的指令。因为A是值类型,编译器不会出现多态行为。因此,直接调用,不装箱。(注:ToString是A的基类System.ValueType的方法)
a.GetType(),GetType是继承于System.ValueType的方法,要调用它,需要一个方法表指针,于是a将被装箱,从而生成方法表指针,调用基类的System.ValueType。(补一句,所有的值类型都是继承于System.ValueType的)。
a.Clone(),因为A实现了Clone方法,所以无需装箱。
ICloneable转型:当a2为转为接口类型时,必须装箱,因为接口是一种引用类型。
c.Clone()。无需装箱,在托管堆中对上一步已装箱的对象进行调用。
附:其实上面的基于一个根本的原理,因为未装箱的值类型没有方法表指针,所以,不能通过值类型来调用其上继承的虚方法。另外,接口类型是一个引用类型。对此,我的理解,该方法表指针类似C++的虚函数表指针,它是用来实现引用对象的多态机制的重要依据。
9:如何更改已装箱的对象
对于已装箱的对象,因为无法直接调用其指定方法,所以必须先拆箱,再调用方法,但再次拆箱,会生成新的栈实例,而无法修改装箱对象。有点晕吧,感觉在说绕口令。还是举个例子来说:(在上例中追加change方法)
public void Change(Int32 x) { this.x = x; }
调用:
A a = new A(); a.x = 100; Object o = a; //装箱成o,下面,想改变o的值 ((A)o).Change(200); //改掉了吗?没改掉没改掉的原因是o在拆箱时,生成的是临时的栈实例A,所以,改动是基于临时A的,并未改到装箱对象。
(附:在托管C++中,允许直接取加拆箱时第一步得到的实例引用,而直接更改,但C#不行。)
那该如何是好?
嗯,通过接口方式,可以达到相同的效果。
实现如下:
interface IChange { void Change(Int32 x); } struct A : IChange { … }
调用:
((IChange)o).Change(200);//改掉了吗?改掉了
为啥现在可以改?
在将o转型为IChange时,这里不会进行再次装箱,当然更不会拆箱,因为o已经是引用类型,再因为它是IChange类型,所以可以直接调用Change,于是,更改的也就是已装箱对象中的字段了,达到期望的效果。
10、将值类型转换为引用类型,需要进行装箱操作(boxing):
首先从托管堆中为新生成的引用对象分配内存
然后将值类型的数据拷贝到刚刚分配的内存中
返回托管堆中新分配对象的地址
可以看出,进行一次装箱要进行分配内存和拷贝数据这两项比较影响性能的操作。
将引用类型转换为值类型,需要进行拆箱操作(unboxing):
首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱。
将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。
经过这2步,可以认为是同boxing是互反操作。严格意义上的拆箱,并不影响性能,但伴随这之后的拷贝数据的操作就会同boxing操作中一样影响性能。
11、
NET的所有类型都是由基类System.Object继承过来的,包括最常用的基础类型:int, byte, short,bool等等,就是说所有的事物都是对象。
如果申明这些类型得时候都在堆(HEAP)中分配内存,会造成极低的效率!(个中原因以及关于堆和栈得区别会在另一篇里单独得说说!)
.NET如何解决这个问题得了?正是通过将类型分成值型(value)和引用型(regerencetype),
C#中定义的值类型和引用类型
值类型:原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)
引用类型:类、数组、接口、委托、字符串等
值型就是在栈中分配内存,在申明的同时就初始化,以确保数据不为NULL;
引用型是在堆中分配内存,初始化为null,引用型是需要GARBAGE COLLECTION来回收内存的,值型不用,超出了作用范围,系统就会自动释放!
下面就来说装箱和拆箱的定义!
装箱就是隐式的将一个值型转换为引用型对象。比如:
int i=0; Syste.Object obj=i;
这个过程就是装箱!就是将i装箱!
拆箱就是将一个引用型对象转换成任意值型!比如:
int i=0; System.Object obj=i; int j=(int)obj;
这个过程前2句是将i装箱,后一句是将obj拆箱!
更多c#装箱和拆箱知识整理相关文章请关注PHP中文网!