.NET 文字列が値型であるかのように装う
-
11-09-2019 - |
質問
.NET では、文字列は不変であり、参照型変数です。これは、新しい .NET 開発者にとってはしばしば驚きであり、その動作により値型オブジェクトと間違える可能性があります。ただし、使い方の練習以外にも、 StringBuilder
長い連結の場合は特に。ループでは、実際にこの区別を知る必要がある理由はありますか?
.NET 文字列とそれらを値型であるかのように装っている/誤解しているだけですか?
解決
のデザイン string
s は、プログラマとしてあまり心配する必要がないように意図的に設計されています。これは、多くの状況において、文字列への別の参照が存在し、それが同時に変更される場合 (オブジェクト参照の場合のように) 考えられる複雑な結果についてあまり考えずに、文字列の割り当て、移動、コピー、変更を行うだけでよいことを意味します。
メソッド呼び出しの文字列パラメータ
(編集:このセクションは後で追加されました)
文字列がメソッドに渡される場合、それらは参照によって渡されます。メソッド本体で読み取られるだけの場合は、特別なことは何も起こりません。ただし、変更されるとコピーが作成され、一時変数がメソッドの残りの部分で使用されます。このプロセスはと呼ばれます コピーオンライト.
ジュニアにとって問題となるのは、オブジェクトが参照であり、渡されたパラメータを変更するメソッドでオブジェクトが変更されるという事実に慣れていることです。文字列で同じことを行うには、 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 と文字列のパフォーマンスの詳細
編集:Konrad Rudolph からのコメントに基づいて、このセクションを追加しました。
前述の経験則に疑問を感じる場合は、次のもう少し詳しい説明を検討してください。
- 多数の小さな文字列を追加する StringBuilder は、文字列の連結をかなり早く上回ります (30、50 回の追加) が、2μs では、100% のパフォーマンス向上さえ無視できることがよくあります (まれな状況では安全です)。
- 大きな文字列 (80 文字以上の文字列) を追加する StringBuilder が文字列の連結を上回るのは、数千回、場合によっては数千分の 100 回繰り返した後でのみであり、多くの場合、その差はわずか数パーセントです。
- 文字列アクション (置換、挿入、部分文字列、正規表現など) を混合すると、多くの場合、StringBuilder または文字列連結の使用が同等になります。
- 定数の文字列連結はコンパイラ、CLR、または JIT によって最適化できますが、StringBuilder では最適化できません。
- コードには連結が混在することがよくあります
+
,StringBuilder.Append
,String.Format
,ToString
などの文字列操作を行う場合、そのような場合に StringBuilder を使用することはほとんど効果的ではありません。
そうするとき は 効率的ですか?多数の小さな文字列が追加される場合、つまり、データをファイルにシリアル化する場合など、StringBuilder に「書き込まれた」データを変更する必要がない場合。また、StringBuilder は参照型であり、文字列が変更されるとコピーされるため、多くのメソッドで何かを追加する必要がある場合もあります。
インターンされた文字列について
ジュニア プログラマに限らず、問題は、一見同じ状況で参照比較を試みたときに、結果が true である場合もあれば false である場合もあることが判明したときに発生します。どうしたの?文字列がコンパイラによってインターンされ、静的にインターンされた文字列のグローバル プールに追加されると、2 つの文字列間の比較で同じメモリ アドレスを指すことができます。2 つの等しい文字列 (1 つはインターンされ、もう 1 つはインターンされていない) を比較すると、 false が返されます。使用 =
比較、または Equals
そしてふざけないでください ReferenceEquals
文字列を扱うとき。
String.Empty 上
同じリーグでは、使用時に時々発生する奇妙な動作に適合します String.Empty
:静的な String.Empty
は常にインターンされますが、値が割り当てられた変数はインターンされません。ただし、デフォルトではコンパイラは次のように割り当てます。 String.Empty
そして同じメモリアドレスを指します。結果:変更可能な文字列変数 (以下と比較した場合) ReferenceEquals
, は true を返しますが、代わりに 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 のパフォーマンスに関するセクションを追加しました。
他のヒント
本当にほとんどのコードのために重要なだけで区別がnull
が文字列変数に割り当てることができるという事実である。
不変クラスは、すべての一般的な状況で値型のような役割を果たし、そして、あなたは違いについてあまり気にせず、プログラミングのかなり多くを行うことができます。
それはあなたが少し深く掘るとあなたは区別のために実際に使用していた性能を気にする場合です。例えば、文字列のコピーが作成されたかのようにメソッドのパラメータとして文字列を渡すと働きますが、コピーは、実際には行われないことを知っています。これは、文字列が実際に(VB6のような?)値型な言語に慣れ人々にとって驚きであるかもしれない、とパラメータとして文字列の多くを渡すと、パフォーマンスのために良いではありません。
ストリングは特別な品種です。これらは参照型ですが、ほとんどのプログラマーによって値型として使用されます。これを不変にし、インターン プールを使用することで、純粋な値型の場合は膨大になるメモリ使用量が最適化されます。
詳細はこちら:
C# .NET String オブジェクトは本当に参照によるのでしょうか?SOで
MSDN の String.Intern メソッド
MSDN の文字列 (C# リファレンス)
アップデート:
を参照してください。 abel
この投稿に対する さんのコメント。私の誤解を招く発言を修正してくれました。