文字列は、不変で、==オーバーロードされて同じオブジェクトを参照していることを確認せずにテキストを比較するなど、値型のほとんどの特性を備えていますが、参照型です。
なぜ文字列は単なる値型ではないのですか?
std::string
コレクションのように動作させるという決定は、現在修正できない古い間違いです。
文字列は、不変で、==オーバーロードされて同じオブジェクトを参照していることを確認せずにテキストを比較するなど、値型のほとんどの特性を備えていますが、参照型です。
なぜ文字列は単なる値型ではないのですか?
std::string
コレクションのように動作させるという決定は、現在修正できない古い間違いです。
回答:
文字列は巨大になる可能性があるため値型ではなく、ヒープに格納する必要があります。値の型は(まだCLRのすべての実装で)スタックに格納されています。スタック割り当て文字列は、あらゆる種類のことを壊します:スタックは32ビットの場合は1 MBのみ、64ビットの場合は4 MBです。各文字列をボックス化する必要があり、コピーペナルティが発生し、文字列をインターンできず、メモリ使用量もありません。気球など
(編集:値の型のストレージが実装の詳細であるという説明が追加されました。これにより、System.ValueTypeから継承されない値のセマティックを持つ型があるという状況が発生します。ありがとう。Ben。)
String
は可変サイズではありません。追加すると、実際には別のString
オブジェクトが作成され、そのオブジェクトに新しいメモリが割り当てられます。
Int32
は常に4バイトであるため、文字列変数を定義するたびに、コンパイラは4バイトを割り当てます。int
変数(値型の場合)を検出したときに、コンパイラーはどのくらいのメモリーを割り当てる必要がありますか?その時点ではまだ値が割り当てられていないことを理解してください。
Int32
は常に4バイトなので、int
変数を定義するたびに、コンパイラは4バイトを割り当てます。string
変数(値型の場合)を検出したときに、コンパイラーはどのくらいのメモリーを割り当てる必要がありますか?その時点ではまだ値が割り当てられていないことを理解してください。
値型であり、メソッドに渡されたりメソッドから返されるたびに値をコピーする必要がある場合、パフォーマンス(空間と時間!)はひどいため、値型ではありません。
それは世界を正気に保つための価値の意味論を持っています。コード化するのがどれほど難しいか想像できますか
string s = "hello";
string t = "hello";
bool b = (s == t);
設定b
しますかfalse
?どんなアプリケーションでもコーディングがどれほど難しいか想像してみてください。
new String("foo");
、別の人new String("foo")
が同じ参照で評価できるため、new
演算子が期待するものとは異なる種類のものである場合があります。(または、参照を比較したい場合を教えていただけますか?)
ReferenceEquals(x, y)
は高速なテストであり、すぐに0を返すことができます。また、nullテストと組み合わせると、それ以上の作業は必要ありません。
string
null参照としてではなく、空の文字列(以前の.netシステムの場合)として動作することを意味します。実際、私自身の好みはString
reference- type を含む値の型をNullableString
持つことです。前者は同等のデフォルト値をString.Empty
持ち、後者はデフォルトのnull
を持ち、特別なボックス化/ボックス化解除ルール(デフォルトのボックス化など)を持ちます。を評価NullableString
すると、String.Empty
)への参照が生成されます。
参照型と値型の違いは、基本的に言語の設計におけるパフォーマンスのトレードオフです。参照型はヒープ上に作成されるため、参照型は構築と破棄、およびガベージコレクションにある程度のオーバーヘッドがあります。一方、値型はメソッドの呼び出しにオーバーヘッドがあります(データサイズがポインターよりも大きい場合)。これは、ポインターだけではなくオブジェクト全体がコピーされるためです。文字列はポインタのサイズよりもはるかに大きくなる可能性があるため(通常はそれよりも大きいため)、参照型として設計されています。また、Servyが指摘したように、値型のサイズはコンパイル時にわかっている必要がありますが、これは文字列の場合とは限りません。
可変性の問題は別の問題です。参照型と値型はどちらも変更可能または不変にすることができます。ただし、可変値型のセマンティクスは混乱を招く可能性があるため、値型は通常不変です。
参照型は一般に変更可能ですが、意味がある場合は不変として設計できます。文字列は、特定の最適化を可能にするため、不変として定義されます。たとえば、同じプログラムで同じ文字列リテラルが複数回出現する場合(これはよくあることです)、コンパイラは同じオブジェクトを再利用できます。
では、なぜ「==」がオーバーロードされて文字列をテキストで比較するのでしょうか。最も有用なセマンティクスだからです。2つの文字列がテキストで等しい場合、最適化のために同じオブジェクト参照である場合とそうでない場合があります。したがって、参照を比較することはほとんど役に立ちませんが、テキストを比較することはほとんど常に必要なことです。
より一般的に言えば、文字列には値セマンティクスと呼ばれるものがあります。これは、C#固有の実装詳細である値型よりも一般的な概念です。値型には値のセマンティクスがありますが、参照型にも値のセマンティクスがある場合があります。型が値のセマンティクスを持っている場合、基礎となる実装が参照型であるか値型であるかを実際には判別できないため、実装の詳細を検討できます。
string
タイプには、固定サイズのcharバッファーが必要です。これは、制限的であり、非常に非効率的です。
これは古い質問に対する遅い回答ですが、他のすべての回答には要点がありません。つまり、.NETには2005年の.NET 2.0までジェネリックがなかったということです。
String
などの非ジェネリックコレクションに文字列を最も効率的な方法で格納できるようにすることがMicrosoftにとって非常に重要であったため、は値型ではなく参照型System.Collections.ArrayList
です。
ジェネリックでないコレクションに値タイプを格納するにobject
は、ボックス化と呼ばれるタイプへの特別な変換が必要です。CLRは、値の型をボックス化するときに、値をa内にラップSystem.Object
して、マネージヒープに格納します。
コレクションから値を読み取るには、ボックス化解除と呼ばれる逆の操作が必要です。
ボックス化とボックス化解除の両方に無視できないコストがあります。ボックス化には追加の割り当てが必要で、ボックス化解除には型チェックが必要です。
一部の回答は誤ってそれを主張します string
は、サイズが可変であるため、値型として実装することはできないます。実際、Small String Optimization戦略を使用して、文字列を固定長データ構造として実装するのは簡単です。文字列は、外部バッファーへのポインターとして格納される大きな文字列を除いて、Unicode文字のシーケンスとして直接メモリに格納されます。両方の表現は、同じ固定長、つまりポインタのサイズを持つように設計できます。
ジェネリックが初日から存在していた場合、値の型として文字列を使用する方が、よりシンプルなセマンティクス、より優れたメモリ使用量、より優れたキャッシュの局所性を備えた、おそらくより良いソリューションだったと思います。A List<string>
だけ小さな文字列を含むが、メモリの単一の連続ブロックされている可能性が。
string
そのサイズとchar
配列へのポインタのみが含まれているため、「巨大な値の型」にはなりません。しかし、これがこの設計決定の単純で適切な理由です。ありがとう!
文字列だけが不変の参照型ではありません。 マルチキャストデリゲートも。 それが書いても安全な理由です
protected void OnMyEventHandler()
{
delegate handler = this.MyEventHandler;
if (null != handler)
{
handler(this, new EventArgs());
}
}
これは文字列を操作してメモリを割り当てる最も安全な方法であるため、文字列は不変であると思います。なぜ値型ではないのですか?以前の作者はスタックサイズなどについて正しいです。プログラムで同じ定数文字列を使用する場合、文字列を参照型にすることでアセンブリサイズを節約できることも付け加えておきます。定義すると
string s1 = "my string";
//some code here
string s2 = "my string";
「my string」定数の両方のインスタンスがアセンブリに1回だけ割り当てられる可能性があります。
通常の参照型のように文字列を管理する場合は、新しいStringBuilder(string s)内に文字列を配置します。または、MemoryStreamsを使用します。
ライブラリを作成する予定で、関数に巨大な文字列が渡されることが予想される場合は、パラメーターをStringBuilderまたはStreamとして定義します。
string
参照型であることをどのように見分けることができますか?実装方法が重要かどうかはわかりません。C#の文字列は正確に不変であるため、この問題を心配する必要はありません。
実際、文字列は値の型にほとんど似ていません。まず、すべての値の型が不変であるとは限りません。必要に応じてInt32の値を変更しても、スタック上の同じアドレスのままです。
文字列は非常に良い理由で不変であり、それが参照型であることとは何の関係もありませんが、メモリ管理とは関係があります。文字列のサイズが変更されたときに新しいオブジェクトを作成する方が、マネージヒープで移動するよりも効率的です。値/参照型と不変オブジェクトの概念を組み合わせていると思います。
「==」まで:あなたが言ったように「==」は演算子のオーバーロードであり、文字列を操作するときにフレームワークをより便利にするために、これも非常に適切な理由で実装されました。
文字列が文字配列で構成されているほど単純ではありません。文字列を文字配列として見ます[]。したがって、参照メモリの場所はスタックに格納されており、ヒープ上の配列のメモリの場所の先頭を指しているため、ヒープ上にあります。文字列のサイズは、割り当てられるまで不明です。ヒープに最適です。
同じサイズの文字列を変更した場合、コンパイラはそれを認識せず、新しい配列を割り当てて配列内の位置に文字を割り当てる必要があるため、文字列は実際には不変です。文字列を、言語がメモリをその場で割り当てる必要がないように保護する方法であると考えると理にかなっています(プログラミングのようにCを読み取る)。
さらに別の不可解な反対票を投じるリスクがあります...多くの人が値の型とプリミティブ型に関してスタックとメモリについて言及しているという事実は、それらがマイクロプロセッサのレジスタに適合しなければならないためです。レジスタのビット数よりもビット数が多い場合、スタックに何かをプッシュしたりポップしたりすることはできません。命令は、たとえば「pop eax」です。32ビットシステムではeaxが32ビット幅であるためです。
浮動小数点プリミティブ型は、80ビット幅のFPUによって処理されます。
これはすべて、プリミティブ型の定義を難読化するOOP言語が登場するずっと前に決定されたもので、値型はOOP言語専用に作成された用語であると思います。
is
テストを省略)であるため、答えはおそらく「歴史的な理由」にあります。不変オブジェクトを物理的にコピーする必要がないため、コピーのパフォーマンスが理由になることはありません。現在、実際にis
チェック(または同様の制約)を使用するコードを壊さずに変更することは不可能です。