7

.NETでは、文字列は不変であり、参照型変数です。これは、動作が原因で値型オブジェクトと間違える可能性のある新しい.NET開発者にとっては驚きとなることがよくあります。StringBuilderただし、特に長い連結に使用する方法以外は。ループの中で、この区別を知る必要がある実際の理由はありますか?

.NET文字列に関する値参照の違いを理解することと、値型であると偽ったり誤解したりすることによって、どのような現実のシナリオが助けられたり回避されたりしますか?

4

4 に答える 4

17

sの設計はstring、プログラマーとしてあまり心配する必要がないように意図的に設計されています。多くの場合、これは、文字列への別の参照が存在し、同時に変更される場合に起こりうる複雑な結果をあまり考えずに、文字列を割り当て、移動、コピー、変更できることを意味します(オブジェクト参照の場合のように)。

メソッド呼び出しの文字列パラメータ

(編集:このセクションは後で追加されます)
文字列がメソッドに渡されるとき、それらは参照によって渡されます。メソッド本体でのみ読み取られる場合、特別なことは何も起こりません。ただし、それらが変更されると、コピーが作成され、メソッドの残りの部分で一時変数が使用されます。このプロセスは、コピーオンライトと呼ばれます。

ジュニアを悩ませているのは、オブジェクトが参照であり、渡されたパラメーターを変更するメソッドで変更されるという事実に慣れていることです。文字列で同じことを行うには、refキーワードを使用する必要があります。これにより、実際には文字列参照を変更して呼び出し元の関数に戻すことができます。そうしないと、メソッド本体で文字列を変更できません。

void ChangeBad(string s)      { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }

// in calling method:
string s1 = "hi";
ChangeBad(s1);       // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1);  // s1 changes to "hello world" on return

StringBuilderの場合

この区別は重要ですが、初心者のプログラマーは通常、それについてあまり知らない方がよいでしょう。多くの文字列の「構築」を行うときに使用StringBuilderするのは良いことですが、多くの場合、アプリケーションには揚げる魚がはるかに多く、パフォーマンスの向上StringBuilderはごくわずかです。すべての文字列操作はStringBuilderを使用して実行する必要があると言うプログラマーには注意してください。

非常に大まかな経験則として、StringBuilderにはある程度の作成コストがかかりますが、追加は安価です。文字列の作成コストは安いですが、連結は比較的高価です。ターニングポイントは、サイズにもよりますが、約400〜500の連結です。その後、StringBuilderがより効率的になります。

StringBuilderと文字列のパフォーマンスの詳細

編集:コンラッド・ルドルフからのコメントに基づいて、私はこのセクションを追加しました。

前の経験則で疑問に思われる場合は、次のもう少し詳細な説明を検討してください。

  • 多くの小さな文字列の追加を含むStringBuilderは、文字列の連結をかなり速く上回ります(30、50の追加)が、2µsでは、100%のパフォーマンスの向上でさえ無視できることがよくあります(まれな状況では安全です)。
  • いくつかの大きな文字列追加(80文字以上の文字列)を備えたStringBuilderは、数千回、場合によっては数十万回の反復後にのみ文字列の連結を上回り、その差は多くの場合わずか数パーセントです。
  • 文字列アクション(置換、挿入、部分文字列、正規表現など)を混在させると、StringBuilderまたは文字列連結の使用が等しくなることがよくあります。
  • 定数の文字列連結は、コンパイラ、CLR、またはJITによって最適化できますが、StringBuilderでは最適化できません。
  • コードは、連結、、、およびその他の文字列操作を混合することがよくあります。+そのような場合にStringBuilderを使用しても、効果はほとんどありません。StringBuilder.AppendString.FormatToString

それで、それはいつ効率的ですか?たとえば、データをファイルにシリアル化するためなど、多くの小さな文字列が追加されている場合や、StringBuilderに「書き込まれた」後に「書き込まれた」データを変更する必要がない場合。また、StringBuilderは参照型であり、文字列が変更されるとコピーされるため、多くのメソッドが何かを追加する必要がある場合。

インターンされた文字列について

ジュニアプログラマーだけでなく、参照比較を行おうとして、同じように見える状況で結果が真である場合と偽である場合があることを発見すると、問題が発生します。どうしたの?文字列がコンパイラによってインターンされ、文字列のグローバル静的インターンプールに追加された場合、2つの文字列間の比較は同じメモリアドレスを指す可能性があります。(参照!)2つの等しい文字列を比較すると、1つはインターンされ、もう1つはそうではありません。falseが生成されます。=比較を使用するかEquals、文字列を処理するときに遊んではいけませんReferenceEquals

String.Emptyについて

同じリーグでは、使用時に時々発生する奇妙な動作に適合しますString.Empty。静的String.Emptyは常にインターンされますが、値が割り当てられた変数はインターンされません。ただし、デフォルトでは、コンパイラはString.Empty同じメモリアドレスを割り当ててポイントします。結果:可変文字列変数は、と比較するとReferenceEqualstrueを返しますが、代わりにfalseを期待する場合があります。

// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";

// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);

// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);

深く

あなたは基本的に、初心者にどのような状況が発生する可能性があるかについて尋ねました。object.ReferenceEquals文字列で使用すると信頼できないので、私のポイントは避けることに要約すると思います。その理由は、文字列がコード内で一定である場合に文字列インターンが使用されるためですが、常にではありません。この動作に依存することはできません。String.Empty""は常にインターンされますが、値が変更可能であるとコンパイラーが信じるときではありません。最適化オプション(デバッグとリリースなど)が異なると、結果も異なります。

とにかくいつ必要ですReferenceEqualsか?オブジェクトの場合は意味がありますが、文字列の場合は意味がありません。unsafe文字列を操作する人には、オブジェクトを理解して固定しない限り、文字列の使用を避けるように教えてください。

パフォーマンス

パフォーマンスが重要な場合、文字列は実際に不変でなく、使用することが常に最速のアプローチStringBuilderであるとは限らないことがわかります。

ここで使用した多くの情報は、文字列に関するこの優れた記事と、文字列をインプレースで操作するための「ハウツー」(可変文字列)で詳しく説明されています。

更新:コードサンプルを追加
更新:「詳細」セクションを追加(誰かがこれが役立つことを願っています;)
更新:いくつかのリンクを追加、文字列パラメータに関するセクションを追加更新:文字列から文字列ビルダーに切り替えるタイミングの見積もりを追加
更新
追加セクションを追加Konrad Rudolphによる発言の後、StringBuilderとStringのパフォーマンスについて

于 2009-11-02T00:32:01.350 に答える
3

不変クラスは、すべての一般的な状況で値型のように機能し、違いをあまり気にせずにかなり多くのプログラミングを行うことができます。

区別のために実際に使用するのは、もう少し深く掘り下げてパフォーマンスを気にするときです。たとえば、文字列をパラメータとしてメソッドに渡すと、文字列のコピーが作成されたかのように機能しますが、実際にはコピーは行われません。これは、文字列が実際に値型である言語(VB6?など)に慣れている人にとっては驚きかもしれません。また、パラメーターとして多くの文字列を渡すと、パフォーマンスが低下します。

于 2009-11-02T00:33:43.983 に答える
3

ほとんどのコードで本当に重要な唯一の違いは、null文字列変数に割り当てることができるという事実です。

于 2009-11-02T00:20:55.917 に答える
1

ひもは特別な品種です。これらは参照型ですが、ほとんどのコーダーによって値型として使用されています。不変にし、インターンプールを使用することで、メモリ使用量を最適化します。これは、純粋な値型の場合は膨大になります。

ここでより多くの読み物:
C#.NET文字列オブジェクトは本当に参照によるものですか?on SO
String.Internメソッド(MSDN
文字列)(C#リファレンス)(MSDN)

更新:この投稿へののコメントを
参照してください。abelそれは私の誤解を招く発言を修正しました。

于 2009-11-02T00:17:35.763 に答える