ボックス化とボックス化解除について詳しく知ることは、実際には非常に興味深いことです。まず、ボックス化とボックス化解除がなぜ起こるのか見てみましょう。
次のコードを見てください:
class Program { static void Main(string[] args) { ArrayList array = new ArrayList(); Point p;//分配一个 for (int i = 0; i < 5; i++) { p.x = i;//初始化值 p.y = i; array.Add(p);//装箱 } } } public struct Point { public Int32 x; public Int32 y; }
5 回ループし、毎回 Point 値タイプのフィールドを初期化し、それを ArrayList に入れます。 Structは値型の構造体ですが、ArrayListには何が格納されるのでしょうか? ArrayList の Add メソッドをもう一度見てみましょう。 MSDN で Add メソッドを参照できます:
public virtual int Add(Object value),
Add のパラメータが Object 型であることがわかります。つまり、必要なパラメータはオブジェクトへの参照です。つまり、ここでのパラメータは参照型である必要があります。参照型とは何かについては、詳しく説明する必要はありません。それはヒープ上のオブジェクトへの参照にすぎません。ただし、理解を容易にするために、ヒープとスタックについてもう一度話しましょう。
1. スタック領域 (スタック) - コンパイラーによって自動的に割り当ておよび解放され、関数のパラメーター値、ローカル変数値などが格納されます。
2. ヒープ領域(ヒープ) - プログラマによって割り当てられ、解放されます。プログラマが解放しない場合、プログラムの終了時に OS によって再利用される可能性があります。
例:
class Program { static void Main(string[] args) { Int32 n;//这是值类型,存放在栈中,Int32初始值为0 A a;//此时在栈中开辟了空间 a = new A();//真正实例化后的一个对象则保存在堆中。 } } public class A { public A() { } }
上記の質問に戻りますが、Add メソッドには参照型のパラメーターが必要です。どうすればよいでしょうか?次に、ボックス化を使用する必要があります。いわゆるボックス化とは、値型を参照型に変換することです。変換プロセスは次のとおりです:
1. マネージド ヒープにメモリを割り当てます。割り当てられるメモリ量は、値型の個々のフィールドで必要なメモリ量に、マネージド ヒープ内のすべてのオブジェクトが持つ 2 つの追加メンバー (型オブジェクト ポインターと同期ブロック インデックス) で必要なメモリ量を加えたものです。
2. 値の型フィールドを新しく割り当てられたメモリにコピーします。
3. オブジェクトのアドレスを返します。この時点で、アドレスはオブジェクトへの参照であり、値の型は参照型に変換されています。
このようにして、Add メソッドで、ボックス化された Point オブジェクトへの参照が保存されます。ボックス化されたオブジェクトは、プログラマが処理するか、システムのガベージ コレクションがそれを処理するまで、ヒープ内に残ります。この時点で、ボックス化された値型の有効期間は、ボックス化されていない値型の有効期間を超えます。
上記のボックス化では、配列の 0 番目の要素を取り出したい場合は、当然ボックス化を解除する必要があります。
Point p = (Point)array[0]; ここで行う必要があるのは、参照を取得することです。 ArrayList の要素 0 にそれを Point 値型 p に入れます。この目標を達成するには、どのように実装すればよいでしょうか。まず、ボックス化された Point オブジェクトの各 Point フィールドのアドレスを取得します。開梱はこれで終わりです。これらのフィールドに含まれる値は、ヒープからスタックベースの値型インスタンスにコピーされます。アンボックス化は本質的に、オブジェクトに含まれるプリミティブ値型への参照を取得するプロセスです。実際、参照はボックス化されたインスタンスのボックス化されていない部分を指します。したがって、ボックス化とは異なり、ボックス化解除ではメモリ内のバイトをコピーする必要がありません。ただし、もう 1 つの点があります。ボックス化解除直後にフィールド コピー操作が発生します。
したがって、ボックス化とボックス化解除はプログラムの速度とメモリ消費量に悪影響を与えるため、プログラムがボックス化/ボックス化解除操作を自動的に実行するタイミングに注意し、コードを記述するときにこのような状況を避けるようにしてください。
ボックス化を解除するときは、次の例外に注意してください。
1. 「ボックス化された値型インスタンスへの参照」を含む変数が null の場合、NullReferenceException がスローされます。
2. 参照が指すオブジェクトが期待される値型のボックス化されたインスタンスではない場合、InvalidCastException がスローされます。
たとえば、次のコード スニペット:
Int32 x = 5; Object o = x; Int16 r = (Int16)o;//抛出InvalidCastException异常
Int32 x = 5; Object o = x; //Int16 r = (Int16)o;//抛出InvalidCastException异常 Int16 r = (Int16)(Int32)o;
ボックス化解除後、次のコードに示すように、フィールドのコピーが行われます:
//会发生字段复制 Point p1; p1.x = 1; p1.y = 2; Object o = p1;//装箱,发生复制 p1 = (Point)o;//拆箱,并将字段从已装箱的实例复制到栈中
//要改变已装箱的值 Point p2; p2.x = 10; p2.y = 20; Object o = p2;//装箱 p2 = (Point)o;//拆箱 p2.x = 40;//改变栈中变量的值 o = p2;//再一次装箱,o引用新的已装箱实例
さらにいくつかのボックス化とボックス化解除のコード スニペットを見てみましょう:
//装箱拆箱演示 Int32 v = 5; Object o = v; v = 123; Console.WriteLine(v + "," + (Int32)o);
ただし、上記のコードは変更できます:
Object o = v; v = 123;
この方法では、ボックス化は行われません。
次のコードをもう一度見てください:
//修改后 Console.WriteLine(v.ToString() + "," + o);
ここでは 1 つのボックス化、つまり Object o = v のみが発生します。ただし、Console.WriteLine は int、bool、double などでオーバーロードされているため、ここではボックス化は発生しません。