1. ボックス化とアンボックス化は抽象的な概念です
2. ボックス化は、参照型を値型に変換することです
ボックス化関数とアンボックス化関数を使用すると、値型を参照型にリンクできます。値型の任意の値をオブジェクト型との間で変換できるようにします
例:
int val = 100; object obj = val; Console.WriteLine (“对象的值 = {0}", obj);
これはボックス化プロセスであり、値型を参照型に変換するプロセスです
int val = 100; object obj = val; int num = (int) obj; Console.WriteLine ("num: {0}", num);
これはアンボックス化です値型を参照型に変換し、次に参照型を値型に変換するプロセスです
注: ボックス化されたオブジェクトのみをボックス化解除できます
3.NET では、データ型が分割されます。これに対応して、メモリ割り当ては 2 つの方法に分割され、1 つはスタック、もう 1 つはヒープ (注: マネージド ヒープです) です。スタック上に割り当てられて使用されます。参照型はメモリとマネージド ヒープを割り当てます。
マネージドヒープはガベージコレクションに対応しています。
4: ボックス化/アンボックス化とは何ですか?
ボクシング: 値の型をガベージ コレクション ヒープに保存するために使用されます。ボックス化は、値型をオブジェクト型、またはその値型が実装するインターフェイス型への暗黙的な変換です。
5: 梱包はなぜ必要なのでしょうか? (なぜ値型を参照型に変換するのでしょうか?)
最も一般的なシナリオの 1 つは、オブジェクト型のパラメーターを使用してメソッドを呼び出すことです。オブジェクトは汎用的に使用できる任意の型をサポートします。値の型 (Int32 など) を渡す必要がある場合は、ボックス化が必要です。
6: ボックス化/アンボックス化の内部操作
値型のヒープにオブジェクト インスタンスを割り当て、その値を新しいオブジェクトにコピーします。 3 つの手順に従ってください。
新しく割り当てられたマネージド ヒープ メモリ (サイズは、値型インスタンスにメソッド テーブル ポインターと SyncBlockIndex を加えたサイズです)。
値型のインスタンス フィールドを新しく割り当てられたメモリにコピーします。
これを次のように理解する人もいます。Int32 がボックス化されている場合、返されるアドレスは Int32 を指します。このように理解することは不可能ではないと思いますが、問題があります。第一に、これは包括的ではありません。第二に、Int32 を指すことは (マネージド ヒープ内で) その本質を伝えません。
Unboxing
オブジェクト インスタンスをチェックして、指定された値タイプのボックス化された値であることを確認します。インスタンスの値を値型変数にコピーします。
7: ボックス化/アンボックス化が実行効率に及ぼす影響
どうすればいいですか?
まず第一に、ボクシングを避けるように努めるべきです。たとえば、上記の例 2 の両方の状況は、関数をオーバーロードすることで回避できます。 2 番目のケースはジェネリックを使用することで回避できます。
もちろん、すべてが絶対的なわけではありません。変換したいコードがサードパーティのアセンブリであり、変更できない場合は、それをボックス化することしかできません。
ボックス化/アンボックス化コードの最適化については、C# ではボックス化とアンボックス化が暗黙的に行われるため、基本的な方法はコードを解析することであり、最も直接的な解析方法は原理を理解し、コンパイルされた IL コードの反応を確認することです。
例: ループ本体に冗長なボックス化がある可能性がありますが、最適化のために高度なボックス化を使用するだけで済みます。
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); //改掉了吗?没改掉
(附:在托管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中文网!