ご存じのとおり、 String は不変です。 Stringが不変であり、 StringBuilder classが可変である理由は何ですか?
out
またはref
(オブジェクトではなく参照を変更するため)でない限り、パラメーターにメソッドとして不変型を渡すことによる副作用はありません。したがって、プログラマーは、メソッドの開始時にstring x = "abc"
であり、メソッドの本体で変更されない場合、メソッドの最後でx == "abc"
であることを知っています。"abc" == "ab" + "c"
を意味します。これは不変性を必要としませんが、そのような文字列への参照はその寿命を通して常に「abc」に等しいという事実(これは不変性を必要とします)は、以前の値との同等性を維持することが重要であり、正確性を確保するのがはるかに簡単なキーとして使用しますof(文字列は実際にキーとして一般的に使用されます)。Christmas.AddMonths(1)
は、変更可能なものを変更するのではなく、新しいDateTime
を生成することが理にかなっています。 (別の例として、可変オブジェクトとして名前を変更しても、使用している名前は変更されますが、「Jon」は不変のままであり、他のJonsは影響を受けません。return this
だけでクローンを作成します。とにかくコピーを変更することはできないので、何かが自分のコピーであるふりをしても安全です。全体として、目的の一部として変更を受けていないオブジェクトの場合、不変であることには多くの利点があります。主な欠点は、余分な構造を必要とすることです。ただし、ここでも誇張されていることがよくあります(StringBuilderが固有の構造を持つ同等の一連の連結よりも効率的になる前に、いくつかの追加を行う必要があります)。
可変性がオブジェクトの目的の一部である場合は不利になります(給与が変更されることのないEmployeeオブジェクトによってモデル化される必要がある場合)が、それでも役立つ場合があります(多くのWebやその他のステートレスでアプリケーション、読み取り操作を行うコードは更新を行うコードとは別であり、異なるオブジェクトを使用するのは自然かもしれません。パフォーマンスと正確性保証ゲインに対して不変)。
コピーオンライトは妥協点です。ここで、「実」クラスは「状態」クラスへの参照を保持します。状態クラスはコピー操作で共有されますが、状態を変更すると、状態クラスの新しいコピーが作成されます。これはC#よりもC++でよく使用されます。そのため、std:stringは不変型の利点のすべてではなく一部を享受しながら、可変性を維持します。
文字列を不変にすることには多くの利点があります。自動スレッドセーフを提供し、文字列を単純で効果的な方法で組み込み型のように動作させます。また、実行時に追加の効率(リソースの使用量を削減するために効果的な文字列のインターンを許可するなど)を可能にし、サードパーティのAPI呼び出しで文字列を変更することはできないため、セキュリティ上の大きな利点があります。
不変文字列の1つの大きな欠点に対処するために、StringBuilderが追加されました。不変型の実行時の構築は、GCのプレッシャーの多くを引き起こし、本質的に低速です。これを処理するために明示的で変更可能なクラスを作成することにより、文字列クラスに不要な複雑さを追加することなく、この問題に対処します。
文字列は実際には不変ではありません。彼らはただ公的に不変です。つまり、パブリックインターフェイスから変更することはできません。しかし、内部では実際に可変です。
信じられない場合は、 reflector を使用してString.Concat
定義を見てください。最後の行は...
int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;
ご覧のとおり、FastAllocateString
は空ですが、割り当てられた文字列を返し、FillStringChecked
によって変更されます
実際、FastAllocateString
は外部メソッドであり、FillStringChecked
は安全ではないため、ポインターを使用してバイトをコピーします。
より良い例があるかもしれませんが、これは私がこれまでに見つけたものです。
文字列管理は高価なプロセスです。文字列を不変に保つと、文字列を繰り返し作成するのではなく、再利用できます。
文字列は参照型であるため、コピーされることはありませんが、参照渡しされます。これを、値で渡されるC++ std :: stringオブジェクト(不変ではない)と比較してください。これは、Hashtableのキーとして文字列を使用する場合、C++で問題ないことを意味します。C++は、後で比較するために、ハッシュテーブル(実際にはstd :: hash_mapですが)にキーを格納するために文字列をコピーするためです。したがって、後でstd :: stringインスタンスを変更しても、大丈夫です。しかし、.Netでは、HashtableでStringを使用すると、そのインスタンスへの参照が保存されます。ここで、文字列は不変ではないとしばらくの間仮定し、何が起こるかを見てみましょう。1.誰かがキー「hello」を持つ値xをハッシュテーブルに挿入します。 2. Hashtableは文字列のハッシュ値を計算し、文字列への参照と値xを適切なバケットに配置します。 3.ユーザーがStringインスタンスを「さようなら」に変更します。 4.誰かが「hello」に関連付けられたハッシュテーブルの値を望んでいます。最終的に正しいバケットを検索しますが、文字列を比較すると「bye」!= "hello"と表示されるため、値は返されません。 5.誰かが「さようなら」という値を望んでいるかもしれません。 「さようなら」はおそらく異なるハッシュを持っているので、ハッシュテーブルは別のバケットを見るでしょう。そのバケットには「さようなら」キーがないため、エントリはまだ見つかりません。
ストリングを不変にすることは、ステップ3が不可能であることを意味します。誰かが文字列を変更すると、彼は新しい文字列オブジェクトを作成し、古いものはそのままにします。これは、ハッシュテーブルのキーがまだ「hello」であり、したがって正しいことを意味します。
したがって、おそらく、とりわけ、不変の文字列は、参照によって渡される文字列をハッシュテーブルまたは類似の辞書オブジェクトのキーとして使用できるようにする方法です。
不変データを防御的にコピーする必要はありません。変異させるためにコピーする必要があるという事実にもかかわらず、多くの場合、自由にエイリアスを作成し、このエイリアスの意図しない結果を心配する必要がないため、防御的なコピーがないため、パフォーマンスが向上する可能性があります。
これを投げ込むために、しばしば忘れられがちな見方はセキュリティです。文字列が変更可能な場合、このシナリオを想像してください。
string dir = "C:\SomePlainFolder";
//Kick off another thread
GetDirectoryContents(dir);
void GetDirectoryContents(string directory)
{
if(HasAccess(directory) {
//Here the other thread changed the string to "C:\AllYourPasswords\"
return Contents(directory);
}
return null;
}
渡された文字列を変更することを許可された場合、それが非常に、非常に悪くなる可能性があることがわかります。
文字列は、.NETで参照型として渡されます。
参照型は、スタック上に、マネージヒープにある実際のインスタンスへのポインターを配置します。これは、インスタンス全体をスタックに保持するValueタイプとは異なります。
値型がパラメーターとして渡されると、ランタイムはスタックに値のコピーを作成し、その値をメソッドに渡します。これが、更新された値を返すために 'ref'キーワードで整数を渡す必要がある理由です。
参照型が渡されると、ランタイムはスタック上にポインターのコピーを作成します。コピーされたポインターは、引き続き参照型の元のインスタンスを指します。
文字列型にはオーバーロードされた=演算子があり、ポインターのコピーではなく、それ自体のコピーを作成します。これにより、値型のように動作します。ただし、ポインタのみがコピーされた場合、2番目の文字列操作が別のクラスのプライベートメンバーの値を誤って上書きして、かなり厄介な結果を引き起こす可能性があります。
他の投稿で言及したように、StringBuilderクラスを使用すると、GCのオーバーヘッドなしで文字列を作成できます。
通常、文字列やその他の具体的なオブジェクトは、読みやすさと実行時の効率を向上させるために不変オブジェクトとして表現されます。別のセキュリティは、プロセスが文字列を変更し、文字列にコードを挿入することはできません
可変文字列を関数に渡しますが、変更されるとは思わないことを想像してください。次に、関数がその文字列を変更するとどうなりますか?たとえば、C++では、値による呼び出し(std::string
とstd::string&
パラメーターの違い)だけを行うことができますが、C#では参照がすべてであるため、すべての関数の周りに変更可能な文字列を渡すと、それと予期しない副作用を引き起こします。
これはさまざまな理由の1つにすぎません。パフォーマンスは別のものです(たとえば、インターンされた文字列)。
クラスデータが、格納クラスのコントロールの外で変更できないデータを格納する5つの一般的な方法があります。
文字列は可変長であるため、値型のプリミティブにすることも、文字データを構造体に格納することもできません。残りの選択肢の中で、ある種の不変オブジェクトに文字列の文字データを保存する必要のない唯一の選択肢は#5です。オプション#5を中心にフレームワークを設計することは可能ですが、その選択では、制御外で変更できない文字列のコピーを必要とするコードは、それ自体のプライベートコピーを作成する必要があります。それを行うことはほとんど不可能ではありませんが、それを行うために必要な余分なコードの量、およびすべての防御的なコピーを作成するために必要な余分なランタイム処理の量は、string
可変である、 特に 可変の文字列型(System.Text.StringBuilder
)があり、可変のstring
で達成できることの99%を達成できます。
不変文字列は、並行性関連の問題も防ぎます。
他のスレッドがあなたの背中の後ろで修正している文字列で動作するOSであることを想像してください。コピーを作成せずにどのように検証できますか?