整数のコレクションを作成する.NET 1.0の方法(たとえば)は次のとおりです。
ArrayList list = new ArrayList();
list.Add(i); /* boxing */
int j = (int)list[0]; /* unboxing */
これを使用することのペナルティは、ボックス化とボックス化解除によるタイプセーフティとパフォーマンスの欠如です。
.NET 2.0の方法は、ジェネリックスを使用することです。
List<int> list = new List<int>();
list.Add(i);
int j = list[0];
(私の理解では)ボクシングの代償は、ヒープ上にオブジェクトを作成し、スタックに割り当てられた整数を新しいオブジェクトにコピーし、ボックス化を解除する必要があることです。
ジェネリックの使用はこれをどのように克服しますか?スタックに割り当てられた整数はスタックに残り、ヒープからポイントされますか(範囲外になるとどうなるかというと、そうではありません)。スタックの別の場所にコピーする必要があるようです。
本当に何が起こっているのですか?
コレクションに関して言えば、ジェネリックは実際のT[]
配列を内部で利用することにより、ボックス化/ボックス化解除を回避することを可能にします。たとえば、List<T>
はT[]
配列を使用してその内容を格納します。
arrayはもちろん参照型であるため、(CLRの現在のバージョンでは、yada yadaで)ヒープに格納されます。しかし、それはT[]
であり、object[]
ではないため、配列の要素は「直接」格納できます。つまり、それらはまだヒープ上にありますが、ヒープ上にあります配列ボックス化される代わりに、配列にはボックスへの参照が含まれます。
したがって、たとえばList<int>
の場合、配列内にあるものは次のようになります。
[1 2 3]
これを、object[]
を使用するArrayList
と比較して、次のように「見える」ようにします。
[* a * b * c]
...ここで、*a
などは、オブジェクト(ボックス整数)への参照です。
* a-> 1 * b-> 2 * c-> 3
それらの大雑把なイラストを許してください。うまくいけば、あなたは私が何を意味するのか知っています。
混乱は、スタック、ヒープ、変数の関係を誤解した結果です。これについて考える正しい方法は次のとおりです。
実装の詳細として、短命であることが保証されている格納場所をスタックに割り当てることができます。存続期間が長い可能性のある保管場所がヒープに割り当てられています。これは、「値型は常にスタックに割り当てられる」ことについては何も言っていないことに注意してください。値のタイプはnot常にスタックに割り当てられます:
int[] x = new int[10];
x[1] = 123;
x[1]
は保管場所です。それは長命です。この方法よりも長持ちする可能性があります。したがって、ヒープ上になければなりません。 intが含まれているという事実は関係ありません。
あなたはboxed intが高価である理由を正しく言います:
ボクシングの代償は、ヒープ上にオブジェクトを作成し、スタックに割り当てられた整数を新しいオブジェクトにコピーし、ボックス化を解除する必要があることです。
あなたが間違っているのは、「スタックに割り当てられた整数」と言うことです。整数がどこに割り当てられたかは関係ありません。重要なのは、そのストレージヒープの場所への参照ではなく、ストレージ整数を含むであったことです。価格は、オブジェクトを作成してコピーを行う必要性です。これが関連する唯一のコストです。
それでは、なぜジェネリック変数は高価ではないのでしょうか? T型の変数があり、Tがintになるように構築されている場合、int型、period型の変数があります。 int型の変数は格納場所であり、intが含まれています。 その格納場所がスタック上にあるか、ヒープが完全に無関係であるか。関連するのは、格納場所ヒープ上の何かへの参照ではなく、格納場所intを含むであることです。ストレージの場所にはintが含まれているため、ボックス化とボックス化解除のコストを負担する必要はありません。ヒープに新しいストレージを割り当て、intを新しいストレージにコピーします。
それは今明らかですか?
ArrayListはobject
型のみを処理するため、このクラスを使用するには、object
とのキャストが必要です。値型の場合、このキャストにはボックス化とボックス化解除が含まれます。
ジェネリックリストを使用すると、コンパイラーはその値タイプに特化したコードを出力するため、実際の値は、オブジェクトへの参照ではなくリストに格納されます。値が含まれています。したがって、ボクシングは必要ありません。
(私の理解では)ボクシングの代償は、ヒープ上にオブジェクトを作成し、スタックに割り当てられた整数を新しいオブジェクトにコピーし、ボックス化を解除する必要があることです。
値型は常にスタックにインスタンス化されると想定していると思います。これは当てはまりません-それらはヒープ上、スタック上、またはレジスターのいずれかに作成できます。これについての詳細は、Eric Lippertの記事を参照してください: 真偽値型 。
Genericsを使用すると、リストの内部配列を、実際には_int[]
_ではなく_object[]
_と入力できるため、ボックス化が必要になります。
ジェネリックなしで起こることはここにあります:
Add(1)
を呼び出します。1
_はオブジェクトにボックス化され、ヒープ上に新しいオブジェクトを構築する必要があります。ArrayList.Add()
に渡されます。object[]
_に詰め込まれます。ここには、3つのレベルの間接参照があります:ArrayList
-> _object[]
_-> object
-> int
。
ジェネリックで:
Add(1)
を呼び出します。List<int>.Add()
に渡されます。int[]
_に詰め込まれます。したがって、間接参照のレベルは2つしかありません:_List<int>
_-> _int[]
_-> int
。
他のいくつかの違い:
.NET 1では、Add
メソッドが呼び出されると、次のようになります。
i
変数の内容が参照にコピーされます.NET 2の場合:
i
のコピーがAdd
メソッドに渡されますはい、i
変数は引き続きコピーされます(結局のところ、これは値の型であり、値の型は常にメソッドのパラメーターであっても常にコピーされます)。しかし、ヒープ上に冗長コピーが作成されることはありません。
なぜあなたはWHERE
の観点から値\オブジェクトが保存されていると考えていますか? C#では、CLRの選択内容に応じて、値の型をヒープと同様にスタックに格納できます。
ジェネリックの違いがどこにあるかWHAT
はコレクションに格納されます。 ArrayList
の場合、コレクションにはボックス化されたオブジェクトへの参照が含まれ、List<int>
にはint値自体が含まれます。