web-dev-qa-db-ja.com

C#では、なぜStringは値型のように振る舞う参照型なのでしょうか。

Stringは、同じオブジェクトを確実に参照するのではなく、不変であることやテキストを比較するために==をオーバーロードすることなど、値型のほとんどの特性を備えていますが、参照型です。

なぜ文字列が単なる値型ではないのですか

332
Davy8

文字列は巨大になる可能性があるので値型ではなく、ヒープに格納する必要があります。値型は(まだCLRのすべての実装において)スタックに格納されています。スタック割り当て文字列はあらゆる種類のものを壊すでしょう:スタックは32ビットのためのわずか1MBと64ビットのための4MBです、あなたはコピーのペナルティを招き、あなたは文字列をインターンできませんでした、そしてメモリ使用量バルーンなど.

(編集:値型の格納が実装の詳細であることの説明を追加しました。これにより、値の意味がSystem.ValueTypeから継承されない型になります。ありがとうBen。

304
codekaizen

それが値型であり、その値がメソッドなどに渡されたり返されたりするたびにコピーされなければならない場合、パフォーマンス(スペースと時間)はひどいものになるので、値型ではありません。

世界を健全に保つためには、値の意味があります。あなたがコーディングするのがどれほど難しいか想像できますか?

string s = "hello";
string t = "hello";
bool b = (s == t);

bfalseに設定しますか?どんなアプリケーションでもコーディングがどれほど難しいかを想像してみてください。

54
jason

参照型と値型の違いは、基本的に言語設計におけるパフォーマンスのトレードオフです。参照型はヒープ上に作成されるため、構築と破棄、およびガベージコレクションにオーバーヘッドがあります。一方、データ型がポインタよりも大きい場合、値型はメソッド呼び出しにオーバーヘッドがあります。ポインタ全体ではなくオブジェクト全体がコピーされるためです。文字列はポインタのサイズよりもはるかに大きくなる可能性があるため(通常はそれよりも大きくなることがあるため)、参照型として設計されています。また、Servyが指摘したように、値型のサイズはコンパイル時にわかっていなければなりません。これは文字列の場合は必ずしもそうではありません。

可変性の問題は別の問題です。参照型と値型はどちらも可変または不変のどちらでもかまいません。可変値型のセマンティクスは混乱を招く可能性があるため、値型は通常不変です。

参照型は一般的に変更可能ですが、意味がある場合は不変として設計することもできます。文字列は、特定の最適化を可能にするため、不変として定義されています。たとえば、同じ文字列リテラルが同じプログラム内で複数回出現する場合(これは非常に一般的です)、コンパイラは同じオブジェクトを再利用できます。

それでは、なぜ文字列をテキストで比較するために "=="がオーバーロードされているのでしょうか。それが最も有用な意味論だからです。 2つの文字列がテキストで等しい場合、それらは最適化のために同じオブジェクト参照である場合とそうでない場合があります。テキストを比較することはほとんど常にあなたが望むものですが、参照を比較することはかなり無駄です。

より一般的に言えば、Stringsはvalue semanticsと呼ばれるものを持っています。これは、C#固有の実装の詳細である値型よりも一般的な概念です。値型は値の意味を持ちますが、参照型は値の意味を持つこともできます。型が値セマンティクスを持つとき、基になる実装が参照型と値型のどちらであるかを実際には判断できないため、その実装の詳細を検討できます。

24
JacquesB

これは古い質問に対する遅れた回答ですが、他のすべての回答にはその点が欠けています。つまり、2005年の.NET 2.0まで.NETには総称がないということです。

Stringは、値型ではなく参照型です。文字列を非総称コレクションに最も効率的な方法で格納できるようにすることがMicrosoftにとって非常に重要だったためですSystem.Collection.ArrayListなど。

非ジェネリックコレクションに値型を格納するには、ボクシングと呼ばれるobject型への特別な変換が必要です。 CLRが値型をボックス化すると、その値をSystem.Object内にラップし、それをマネージヒープに格納します。

コレクションから値を読み取るには、ボックス化解除と呼ばれる逆の操作が必要です。

ボクシングとアンボクシングはどちらも無視できないほどのコストがかかります。ボクシングには追加の割り当てが必要です。

stringはサイズが可変であるため、値型として実装されたことがない可能性があると誤って主張する回答もあります。実際には、Small String Optimization戦略を使用して文字列を固定長データ構造として実装するのが簡単です。文字列は、外部バッファへのポインタとして格納される大きな文字列を除いて、一連のUnicode文字として直接メモリに格納されます。両方の表現は、同じ固定長、すなわちポインタのサイズを有するように設計することができる。

ジェネリックが初日から存在していたならば、私はおそらくより単純な意味論、より良いメモリ使用量とより良いキャッシュ局所性で、値型として文字列を持つことがより良い解決策であったと思います。小さな文字列のみを含むList<string>は、単一の連続したメモリブロックである可能性があります。

11
ZunTzu

文字列だけが不変の参照型ではありません。 マルチキャストの代議員もだからこそ、書くのが安全なのです。

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"定数の両方のインスタンスがあなたのアセンブリに一度だけ割り当てられるでしょう。

通常の参照型のように文字列を管理したい場合は、その文字列を新しいStringBuilder(string s)の中に置きます。またはMemoryStreamsを使用してください。

巨大な文字列が関数に渡されることが予想されるライブラリを作成する場合は、パラメータをStringBuilderまたはStreamとして定義します。

8
Bogdan_Ch

また、文字列の実装方法(プラットフォームごとに異なります)と、それらを一緒に縫い始めるときも同様です。 StringBuilderを使うのと同じです。それはあなたがコピーするためのバッファを割り当てます、あなたが最後に達すると、それはあなたがあなたのためにさらに多くのメモリを割り当てます。

たぶんJon Skeetがここで手伝ってくれる?

6
Chris

これは主にパフォーマンスの問題です。

文字列をLIKE値型として振る舞わせることは、コードを書くときに役立ちますが、それを値型にすることは、パフォーマンスに大きな影響を与えます。

詳しくは、.netフレームワークの文字列について Nice article をご覧ください。

5
Denis Troller

stringが参照型であることをどうやって確認できますか?それがどのように実装されているかが重要かどうかはわかりません。 C#の文字列は正確に不変なので、この問題について心配する必要はありません。

2

実際には、文字列は値型とほとんど似ていません。初心者にとって、すべての値型が不変というわけではないので、Int32の値は必要に応じて変更でき、それでもスタック上の同じアドレスになります。

文字列は非常に正当な理由で不変です。それはそれが参照型であることとは何の関係もありませんが、メモリ管理と関係があります。文字列サイズが変更されたときに新しいオブジェクトを作成する方が、管理対象ヒープ上で移動するよりも効率的です。値型/参照型と不変オブジェクトの概念を混在させていると思います。

"=="に関して:あなたが言ったように "=="は演算子のオーバーロードです、そしてまたそれは文字列を扱うときフレームワークをもっと便利にする非常に良い理由のために実装されました。

2
WebMatrix

非常に単純な言葉では、一定のサイズを持つ値はすべて値型として扱うことができます。

2
saurav.net

文字列が文字配列で構成されているほど単純ではありません。私は文字列を文字配列として見ています[]。したがって、参照メモリ位置はスタックに格納され、ヒープ上のアレイのメモリ位置の先頭を指すので、それらはヒープ上にあります。文字列のサイズは割り当てられる前にはわかりません。ヒープには最適です。

同じサイズであっても文字列を変更すると、それを知らなくて、新しい配列を割り当て、配列内の位置に文字を割り当てなければならないため、文字列は本当に不変です。あなたが言語があなたがその場でメモリを割り当てることからあなたを保護する方法としてストリングを考えるならそれは理にかなっている(プログラミングのようにCを読む)

1
BionicCyborg