ゆうなんとかさんの雑記帳的な。

Twitterで踊ったり音ゲーしたりしてるあの名前がよくわからない人が書いてるらしいよ。

C#の文字列に関する覚え書き

たぶんほかの言語でも似たようなのあるはず

あれ値変えられないから

はい。変えられません。通な言い方をすればImmutableというやつです。え?str += hoge;とかできるじゃんって?できますが、そのたびに新しい文字列を作っています。厳密には引用の通り一部はプールされているのですが、長い文字列はほぼほぼ作り直しになります。

共通言語ランタイムは、インターン プールと呼ばれるテーブルを保持することで文字列のストレージを管理しています。このテーブルには、プログラム内で宣言または作成された一意のリテラル文字列に対する単一の参照が含まれています。 この結果として、特定の値を持つリテラル文字列のインスタンスは、システムに 1 つしか存在しません
たとえば、いくつかの変数に同じリテラル文字列を代入した場合、ランタイムはそのリテラル文字列に対する同じ参照をインターン プールから取得して、それぞれの変数に代入します。

String.Intern メソッド (System)

ひとつやふたつ連結するくらいならいいのですが、ループ中に += で連結なんてことをやった日には、ほぼ確実につなげるたびにほとんど似たような文字列ガンガン作っていくことになります。終盤で文字列が長くなってくるとプールされている文字列に当たる確率も下がってくるので無駄が多いですね。

Stringクラスの静的メソッド使うとましになるよ

string.Format

ダブルクオーテーションや+を打ちまくるのが面倒だと思ったら、まずはこれを使ったほうがいいです。目安はリテラルと変数を合わせて4つ以上の文字列をつなげようと思ったときかなと思います。ガベージが云々とかいうのを抜きにしても、単純に見やすくなるというメリットもあります。

var concat = a + " says, \"" + says + "\".";
var format = string.Format("{0} says \"{1}\".", a, says); 

string.Concat

用意した文字列の一群を順番につなぎたいのであればこれを使いましょう。ループさせて += で連結とかもう考えないようにしましょう。いつの間にかIEnumerableを引数に取れるオーバーロードが追加されているのでLINQとの親和性も高いくなりました。ReadLineAsyncとかで順番に読んでいっているときなんかは後で紹介するStringBuilderを使うか、読み取り部分だけメソッドに切り出してyieldしてIEnumerableにしてConcatするといいでしょう。

var lines = await ReadLines(hoge); // IEnumerable<string>を返すと思ってください
var concat = string.Concat(lines);

string.Join

クエリの組み立てみ立てやライブラリを使うほどでもないけれどCSVを吐き出したいみたいなときに使えます。さっきのConcatとの違いは、つなぎ合わせる文字列の間に挟み込む文字列を指定できることです。さっきの例でいうと、&や,を指定しておくと幸せになれるという算段です。こちらもいつの間にかIEnumerableを引数に取れるようになったので(ry

var queries = await ReadLines(hoge); // IEnumerable<string>を返すと思ってください
var joins = "?" + string.Join("&", queries);

複雑な連結をするならStringBuilderを使う

こちらは名前から察しの通り、ごみを出さずに文字列を組み立てていくためのクラスです。文字列を組み立てるメソッドは自分自身を返すので、チェーンさせて書けます。すてき。上のどれでも対応できない複雑な連結はこれを使うといいです。

var builder = new StringBuilder(a).Append(" says, \"").Append(says).Append("\"");
var result  = builder.ToString();