StringBuilder 過剰最適化の罠 — C# の文字列結合、いつ += で十分か

StringBuilder 過剰最適化の罠 — C# の文字列結合、いつ += で十分か

みなさんこんにちは!ヒロポンです!

X 見てると、+= で文字列をくっつけてるコードに「StringBuilder 使えよ」って詰められてる人、ほんま多いんですよね。

でも本人は「そこ、ボトルネックちゃうし、別にええやん」って返してる。

これ、分かる人にはグサッと刺さる話で、X でも 300 超えの「いいね」が付いてました。

レビューで「StringBuilder 使え」って機械的に言われた経験、ありませんか??業務系の現場でも、+= を見た瞬間に条件反射で指摘が飛ぶ「StringBuilder 警察」、たまにいるんですよね。

でも、+= が本当に遅いのは特定の場面だけ

それ以外では、可読性を捨ててまで StringBuilder にする意味はありません。

今回は、「いつ += で十分で、いつ StringBuilder が要るのか」の判断軸を腑に落とすのがゴールです。

📊 4手法の網羅的な比較と「10万回ループの実測値」は、別記事 C# 文字列結合のパフォーマンス完全比較 にまとめてあります。基本の網羅はそちら、今回はその逆張り応用編として「過剰最適化しないための判断軸」に絞ります。

目次

なぜ「StringBuilder 使えよ」と言われるのか

まず、指摘そのものが間違ってるわけじゃない。ここから入ります。

C# の stringimmutable(不変) です。一度作った文字列は変更できない。

だから s += "x" は「s に x を足す」んじゃなくて、「s と x を繋いだ新しい文字列を作り直す」動きになる。

これをループで何万回も回すと、毎回その「作り直し」が走って、中間文字列がヒープにどんどん積み上がる。だんだん遅くなる。

StringBuilder は内部の可変バッファに追記していくので、この作り直しが起きません。だからループでの大量結合では StringBuilder が正解。ここはマジで正しい。

ここまでで分かったこと: string は不変なので、ループでの += 連打は中間文字列を量産して遅くなる。StringBuilder はそれを避ける。指摘自体は正しい。

でも、それ本当にボトルネック?

問題は、「ループの大量結合」以外にまで指摘が飛ぶことなんですよね。

3個や5個の文字列を1回繋ぐだけ。画面に出すメッセージを組み立てるだけ。

そういう「少数・1回きり」の結合に StringBuilder を持ち出しても、速度はまず変わりません。むしろ StringBuilder のインスタンス生成のオーバーヘッドで、逆に遅くなることすらある

冒頭で触れた X の声が、まさにここを突いています。他人視点で要約すると、こういう話でした。

+= で文字列を繋いでるのを見て「StringBuilder 使えよ」と言われがちだけど、そこがボトルネックになってないなら別にいい。アルゴリズムが分かってない人ほど、無駄な所を速くしたがる傾向がある。

この「無駄な所を速くしたがる」が核心。早すぎる最適化(premature optimization)ってやつです。

本当に遅いのはたいてい別の場所(N+1 クエリとか、重い計算とか)で、文字列結合が犯人なケースは、ループ大量結合を除けば、そう多くない。

「コードがクソかどうか」と「実際に遅いかどうか」は別の話。

可読性を犠牲にして StringBuilder にする前に、そこが本当にボトルネックなのかを一度疑う。これが業務SEとしては健全です。

ここまでで分かったこと: 少数・1回きりの結合に StringBuilder は要らない(むしろ損)。「遅そう」と「実際に遅い」は別。先に疑うべきは「そこ本当にボトルネック?」。

いつ += で十分で、いつ StringBuilder が要るのか

判断軸を整理します。結合のパターンで見ると、こんな感じで推奨手法が自然に決まります。

C# の文字列結合を、少数固定・書式付き・ループ大量・とりあえず最適化したい のパターン別に、推奨手法(+/補間/string.Format/StringBuilder/まず計測)と理由を並べた判断軸の表

フローにすると、判断はもっとシンプルです。

文字列を結合したい時、結合する数が多い・可変かで分岐し、少数固定なら+や補間で十分、ループ大量なら本当にボトルネックか計測してから StringBuilder を使う、という判断フロー

コードで言うと、こんな感じ。少数なら補間で読みやすく。

// 少数・固定: 補間文字列で十分。読みやすさ優先
string name = "田中";
int age = 42;
string msg = $"ユーザー {name} さん({age}歳)を登録しました";

ループで件数が読めない大量結合のときだけ、StringBuilder に切り替える。

// ループ・大量: ここが StringBuilder の本当の出番
var sb = new System.Text.StringBuilder();
foreach (var row in rows) // rows が数万件
{
    sb.Append(row.Id).Append(',').Append(row.Name).Append('\n');
}
string csv = sb.ToString();

この2つの境界を引ける。それが判断軸の本体です。

ここまでで分かったこと: 少数・固定は + / 補間、ループ・大量だけが StringBuilder の出番。境界は「結合数が多い・可変か」と「本当にボトルネックか」の2問で引ける。

StringBuilder も万能じゃない

逆張りついでに、もう一つ。

StringBuilder にすれば速い、とも限りません。使い方を間違えると、StringBuilder でも遅くなります

代表が Insert(0, ...)、つまり先頭への挿入。これも X で見かけた話で、他人視点でまとめるとこういうケースでした。

長い文字列を組み立てるのに StringBuilder を使ったのはいいけど、Insert で頭にどんどん足していったら、かえって遅くなった。

理由は、StringBuilder の Insert(0, ...) が、挿入位置から後ろの中身を毎回ずらすから。

先頭挿入を繰り返すと、結局 += のループと同じく重い処理になります。

逆順に組み立てたいなら、Append で末尾に足していって最後に1回だけ反転するか、そもそも組み立て順を見直す。

StringBuilder を出した安心感で Insert を多用すると、せっかくの最適化が台無しになる。マジでもったいない。

ここまでで分かったこと: StringBuilder でも Insert(0, ...) の連打は遅い。「StringBuilder にしたから速い」とは限らない。末尾 Append が基本。

俺の現場ではこう使い分けてる

正直に言うと、俺も昔はレビューで「ここ StringBuilder にしましょう」って、わりと機械的に言ってた側でした。

でもある時。

指摘した結合が「毎回きっちり3要素を1回繋ぐだけ」で、試しに計測してみたんですよ。

そしたら + でも StringBuilder でも体感ゼロ・数値もほぼ同じ。可読性は + の方が上なのに……これ何のための指摘やったん??ってなりました。

それ以来、文字列結合のレビューでは「結合数は多いか/可変か」を先に聞くようにしてます。

少数・固定なら + か補間でいい感じに収まる。ループで件数が読めないときだけ StringBuilder を勧める。

「StringBuilder = 正義」じゃなくて、「遅いと分かってから、遅い場所だけ」。

これだけで、いい感じに過剰最適化のレビュー指摘が減って、コードも読みやすくなりました!!

まとめ

C# の文字列結合、過剰最適化しないための判断軸はこうです。

  • 少数・固定の結合+ か補間文字列。可読性優先。StringBuilder は要らない
  • 書式・桁揃えstring.Format か補間文字列
  • ループ・件数可変の大量結合 → StringBuilder。ここが唯一の本当の出番
  • StringBuilder でも Insert(0, ...) は遅い → 末尾 Append が基本
  • 速くする前に計測 → ボトルネックでない所を最適化しても意味がない

「StringBuilder 使えよ」は、ループ大量結合では正しい。でもそれ以外では、可読性を捨てる理由になりません。

詰められても、判断軸を持っていれば落ち着いて返せます。

よくある質問

Q1. 結局、StringBuilder はいつ使えばいいんですか?

ループや繰り返しで、結合する件数が可変かつ大量になるときです。数万件を1つの文字列に組み立てる、みたいな場面。逆に、数個〜十数個を1回繋ぐだけなら + か補間文字列で十分で、StringBuilder はオーバーヘッドのぶん損になることもあります。

Q2. レビューで「StringBuilder 使え」と言われたら、どう返せばいいですか?

「ここは少数・固定の結合なので、可読性を優先して +(補間)にしています。ループでの大量結合になったら StringBuilder に切り替えます」と、判断軸で返すのが落ち着きます。もし相手が「速度が心配」と言うなら、その箇所が本当にボトルネックか一度計測してみる、で合意できます。

Q3. += と + と補間、速度は違うんですか?

少数・1回きりの結合なら、実用上の差はほぼありません。問題になるのは「ループ内で += を何万回も回す」ケースだけです。具体的な数値比較は別記事の C# 文字列結合のパフォーマンス完全比較 に 10万回ループの実測があります。

Q4. 早すぎる最適化って、文字列結合に限った話ですか?

いいえ、もっと一般的な話です。「遅そう」という直感で最適化に走るより、まず計測してボトルネックを特定する方が、たいてい効きます。文字列結合の StringBuilder は、その分かりやすい一例というだけです。

関連記事

以上!

「StringBuilder 警察に詰められた」経験ある人いたら、どんどんシェア待ってるぜ!!


執筆者

バイブス父さん — 業務SE 7年(SIer 正社員2 / フリーランス5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SIer の正社員からフリーランスに転じ、複数のエージェント経由で案件を回してきた経験ベースで「業務SE視点」の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524(日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次