72

整数のコレクションを作成する.NET1.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];

(私の理解では)ボクシングの代償は、ヒープ上にオブジェクトを作成し、スタックに割り当てられた整数を新しいオブジェクトにコピーし、その逆を行う必要があることです。

ジェネリックの使用はこれをどのように克服しますか?スタックに割り当てられた整数はスタックに残り、ヒープからポイントされますか(スコープから外れるとどうなるかという理由で、これは当てはまらないと思います)?スタックのどこかにコピーする必要があるようです。

本当に何が起こっているのですか?

4

6 に答える 6

78

T[]コレクションに関して言えば、ジェネリックは、実際の配列を内部で利用することにより、ボックス化/ボックス化解除を回避することを可能にします。List<T>たとえば、T[]配列を使用してその内容を格納します。

もちろん、arrayは参照型であるため、(現在のバージョンの CLR では yada yada で) ヒープに格納されます。しかし、それは でT[]あり ではないためobject[]、配列の要素は「直接」格納できます。つまり、それらはまだヒープ上にありますが、ボックス化されて配列に への参照が含まれているのではなく、配列のヒープ上にあります。ボックス。

たとえば、 a のList<int>場合、配列にあるものは次のように「見える」でしょう。

[ 1 2 3 ]

これを と比較すると、ArrayListは を使用するobject[]ため、次のように「見えます」:

[ *a *b *c ]

...ここで*a、などはオブジェクトへの参照です(ボックス化された整数):

*a -> 1
*b -> 2
*c -> 3

それらの粗雑なイラストを許してください。うまくいけば、あなたは私が何を意味するかを知っています.

于 2010-12-09T21:04:48.057 に答える
78

あなたの混乱は、スタック、ヒープ、変数の関係を誤解した結果です。これが正しい考え方です。

  • 変数は、型を持つ格納場所​​です。
  • 変数の存続期間は、短い場合も長い場合もあります。「短い」とは「現在の関数が戻るかスローするまで」を意味し、「長い」とは「それより長くなる可能性がある」ことを意味します。
  • 変数の型が参照型の場合、変数の内容は長期保存場所への参照です。変数の型が値型の場合、変数の内容は値です。

実装の詳細として、短命であることが保証されているストレージの場所をスタックに割り当てることができます。ヒープには、長寿命の可能性があるストレージの場所が割り当てられます。これは、「値型は常にスタックに割り当てられる」ことについては何も言っていないことに注意してください。値の型が常にスタックに割り当てられるとは限りません。

int[] x = new int[10];
x[1] = 123;

x[1]保管場所です。それは長命です。この方法よりも長生きする可能性があります。したがって、ヒープ上にある必要があります。int が含まれているという事実は無関係です。

ボックス化されたintが高価な理由を正しく言います:

ボックス化の代償は、ヒープ上にオブジェクトを作成し、スタックに割り当てられた整数を新しいオブジェクトにコピーし、ボックス化解除のためにその逆を行う必要があることです。

間違っているのは、「スタックに割り当てられた整数」と言うことです。整数がどこに割り当てられたかは問題ではありません。重要なのは、ヒープの場所への参照ではなく、そのストレージに整数が含まれていたことです。価格は、オブジェクトを作成してコピーを行う必要性です。それが関連する唯一のコストです。

では、ジェネリック変数のコストがかからないのはなぜでしょうか? T 型の変数があり、T が int として構築されている場合、int 型、期間の変数があります。int 型の変数は格納場所であり、int が含まれています。そのストレージの場所がスタック上にあるかヒープ上にあるかはまったく関係ありません関連するのは、ヒープ上の何かへの参照を含むのではなく、ストレージの場所に int が含まれていることです。ストレージの場所には int が含まれているため、ボックス化とボックス化解除のコスト (ヒープに新しいストレージを割り当て、int を新しいストレージにコピーする) を負担する必要はありません。

それはもう明らかですか?

于 2010-12-09T23:15:09.170 に答える
3

ArrayListは型のみを処理するobjectため、このクラスを使用するには、との間でキャストする必要がありobjectます。値型の場合、このキャストにはボックス化とボックス化解除が含まれます。

ジェネリックリストを使用すると、コンパイラはその値タイプに特化したコードを出力するため、値を含むオブジェクトへの参照ではなく、実際の値がリストに格納されます。したがって、ボクシングは必要ありません。

(私の理解では)ボクシングの代償は、ヒープ上にオブジェクトを作成し、スタックに割り当てられた整数を新しいオブジェクトにコピーし、その逆を行う必要があることです。

値型は常にスタック上でインスタンス化されると想定していると思います。これは当てはまりません。ヒープ、スタック、またはレジスタのいずれかに作成できます。これについての詳細は、Eric Lippertの記事:値型についての真実を参照してください。

于 2010-12-09T21:02:20.137 に答える
3

Generics を使用すると、ボックス化が必要int[]な effective の代わりに、リストの内部配列を型指定できます。object[]

ジェネリックがない場合は次のようになります。

  1. を呼び出しますAdd(1)
  2. 整数1はオブジェクトにボックス化され、ヒープ上に新しいオブジェクトを構築する必要があります。
  3. このオブジェクトは に渡されArrayList.Add()ます。
  4. ボックス化されたオブジェクトは に詰め込まれますobject[]

ここには 3 つのレベルの間接性があります: ArrayList-> object[]-> object-> int

ジェネリックの場合:

  1. を呼び出しますAdd(1)
  2. int 1 が に渡されList<int>.Add()ます。
  3. int は に詰め込まれますint[]

したがって、間接化には 2 つのレベルしかありません: List<int>-> int[]-> int

その他のいくつかの違い:

  • 非ジェネリック メソッドでは、値を格納するために合計 8 または 12 バイト (1 つのポインター、1 つの int) が必要です。1 つの割り当てでは 4/8、もう 1 つの割り当てでは 4 です。そして、これはおそらくアライメントとパディングによるものです。ジェネリック メソッドは、配列内に 4 バイトのスペースしか必要としません。
  • 非ジェネリックな方法では、ボックス化された int を割り当てる必要があります。ジェネリック メソッドにはありません。これはより高速であり、GC チャーンを減らします。
  • 非ジェネリック メソッドでは、値を抽出するためにキャストが必要です。これはタイプセーフではなく、少し遅くなります。
于 2010-12-09T21:06:08.467 に答える
1

.NET 1では、Addメソッドが呼び出されたとき:

  1. スペースはヒープに割り当てられます。新しい参照が作成されます
  2. i変数の内容が参照にコピーされます
  3. 参照のコピーがリストの最後に配置されます

.NET 2の場合:

  1. 変数のコピーがメソッドiに渡されますAdd
  2. そのコピーのコピーがリストの最後に配置されます

はい、i変数は引き続きコピーされます(結局のところ、それは値型であり、値型は常にコピーされます-それらが単なるメソッドパラメーターであっても)。ただし、ヒープ上に冗長コピーは作成されません。

于 2010-12-09T21:03:38.313 に答える
1

WHERE保存されている値\オブジェクトの観点から考えているのはなぜですか? C# では、CLR の選択内容に応じて、値の型をスタックとヒープに格納できます。

ジェネリックが違いを生むのはWHAT、コレクションに格納されていることです。ArrayListコレクションにボックス化されたオブジェクトへの参照が含まれている場合、List<int>int 値自体が含まれています。

于 2010-12-09T21:12:13.587 に答える