C#の開発において、文字列の結合は避けては通れない基本的な操作です。

ユーザーへのメッセージ表示、ログの出力、ファイルパスの生成など、あらゆる場面で文字列操作が行われます。

しかし、C#における文字列は不変(immutable)なオブジェクトであるため、結合の方法を誤るとアプリケーションのパフォーマンスに重大な影響を及ぼす可能性があります。

本記事では、C#で文字列を結合するための代表的な手法から、最新の.NETにおける最適化手法までを網羅的に解説します。

単純な結合からループ内での効率的な処理、さらにはメモリ効率を意識した高度な手法まで、現場で役立つ知識を詳しく紐解いていきましょう。

C#における文字列結合の基本手法

まずは、日常的な開発でよく使われる基本的な結合方法について解説します。

C#には直感的に記述できる方法が複数用意されています。

+ 演算子による結合

最もシンプルで直感的な方法が + 演算子を使用する方法です。

少数の文字列を結合する場合、コードの可読性が高く、最も頻繁に利用されます。

C#
using System;

public class Program
{
    public static void Main()
    {
        string firstName = "Taro";
        string lastName = "Yamada";

        // + 演算子による結合
        string fullName = firstName + " " + lastName;

        Console.WriteLine(fullName);
    }
}
実行結果
Taro Yamada

+ 演算子を使用すると、内部的には string.Concat メソッドが呼び出されます。

コンパイラが最適化を行ってくれるため、数個程度の静的な結合であればパフォーマンス上の問題はほとんどありません。

文字列補間 (String Interpolation)

C# 6.0から導入された文字列補間($記法)は、現在のC#開発における標準的な手法です。

文字列の中に変数を直接埋め込むことができ、可読性が非常に高いのが特徴です。

C#
using System;

public class Program
{
    public static void Main()
    {
        int id = 101;
        string category = "Books";

        // $記法による文字列補間
        string path = $"/api/products/{category}/{id}";

        Console.WriteLine(path);
    }
}
実行結果
/api/products/Books/101

文字列補間は、単なる見た目の簡略化だけでなく、C# 10以降ではDefaultInterpolatedStringHandlerという仕組みにより、不必要なメモリ確保(アロケーション)を抑える最適化が行われるようになっています。

そのため、現代のC#においては最も推奨される結合手法の一つです。

string.Join による配列・リストの結合

特定の区切り文字(カンマや改行など)を挟んで、コレクション内の要素を結合したい場合には string.Join が最適です。

C#
using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var fruits = new List<string> { "Apple", "Banana", "Orange" };

        // カンマ区切りで結合
        string result = string.Join(", ", fruits);

        Console.WriteLine(result);
    }
}
実行結果
Apple, Banana, Orange

このメソッドは、ループを回して手動で + 結合をするよりも遥かに効率的であり、最後の要素の後に不要な区切り文字が付かないように自動で処理してくれます。

大量結合時の救世主:StringBuilder

文字列の結合において、最も注意しなければならないのがループ内での結合です。

C#の string は一度作成されると内容を変更できないため、+ で結合するたびに新しい文字列オブジェクトがメモリ上に生成されます。

これが数千、数万回繰り返されると、ガベージコレクション(GC)に大きな負荷がかかります。

この問題を解決するのが System.Text.StringBuilder クラスです。

StringBuilder の基本的な使い方

StringBuilder は内部的に可変(mutable)なバッファを持っており、文字列を直接書き換えることができます。

C#
using System;
using System.Text;

public class Program
{
    public static void Main()
    {
        // 容量を指定して初期化するとさらに効率的
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < 5; i++)
        {
            sb.Append("Line ");
            sb.Append(i);
            sb.AppendLine(": Processed.");
        }

        string finalResult = sb.ToString();
        Console.WriteLine(finalResult);
    }
}
実行結果
Line 0: Processed.
Line 1: Processed.
Line 2: Processed.
Line 3: Processed.
Line 4: Processed.

StringBuilder を使用すべきタイミング

結合の回数が不確定な場合や、ループ回数が10回を超えるような場合は、StringBuilder の使用を検討してください。

逆に、2〜3個の固定された文字列を結合するだけであれば、StringBuilder のインスタンスを作成するオーバーヘッドの方が大きくなってしまうため、+ や文字列補間の方が適しています。

パフォーマンス比較:どれが一番速いのか?

手法ごとのパフォーマンスの差を理解するために、代表的な3つの手法を比較してみましょう。

手法適した用途メモリ効率可読性
+ 演算子2〜4個程度の単純な結合低(結合のたびに生成)
文字列補間 ($)複雑なフォーマットや変数埋め込み中〜高(最新.NETで最適化)最高
StringBuilder大量(ループ内など)の結合最高
string.Joinリストや配列の区切り文字結合

ベンチマーク的な視点

例えば、1,000回のループで文字列を結合する場合、+ 演算子では1,000個のテンポラリオブジェクトが生成されますが、StringBuilder では内部バッファの拡張が数回行われるだけで済みます。

現代の.NET(.NET 6以降など)では、文字列補間も内部的に非常に高度な最適化が行われており、静的な形式であれば StringBuilder に匹敵する、あるいはそれを超える速度が出ることもあります。

しかし、ループ内での逐次的な追加処理については、依然として StringBuilder が最強の選択肢です。

最新のC#における高度な文字列結合

C#の開発環境は進化を続けており、より高いパフォーマンスが求められるシーン(ゲーム開発や高負荷なWeb APIなど)では、さらに低レイヤーな手法が使われることがあります。

Span<T> と String.Create の活用

.NET Core 以降、Span<T>ReadOnlySpan<char> を利用したメモリ効率の高い操作が可能になりました。

特に string.Create メソッドを使用すると、あらかじめ必要なメモリサイズを確保し、そのメモリ領域に対して直接文字を書き込むことができます。

C#
using System;

public class Program
{
    public static void Main()
    {
        string category = "User";
        int id = 9999;

        // string.Create を使った高度な結合
        // 必要なバッファサイズを事前に計算し、スタック領域などを活用して書き込む
        string result = string.Create(category.Length + 6, (category, id), (span, state) =>
        {
            state.category.AsSpan().CopyTo(span);
            span[state.category.Length] = '_';
            state.id.TryFormat(span.Slice(state.category.Length + 1), out _);
        });

        Console.WriteLine(result);
    }
}
実行結果
User_9999

この手法は、ゼロアロケーション(追加のメモリ割り当てが発生しない)に近い実装を目指す場合に非常に有効です。

ただし、コードの複雑度が増すため、通常の業務アプリケーションで多用する必要はありません。

パフォーマンスがボトルネックとなった際の「切り札」として覚えておくと良いでしょう。

状況別:最適な使い分けガイドライン

これまでに紹介した手法を、どのように使い分けるべきかガイドラインをまとめます。

1. 数個の変数を組み合わせて短い文章を作る場合

文字列補間($”{a} {b}”)を使用してください。

これが最も読みやすく、間違いが少ない方法です。

2. 固定の文字列を並べる場合

+ 演算子を使用してください。

コンパイル時に結合されるため、実行時のコストはゼロになります。

3. ループ内で条件に応じて文字列を組み立てる場合

StringBuilder を使用してください。

特にループ回数が予測できない場合は必須です。

事前に大まかなサイズがわかっている場合は、コンストラクタで Capacity を指定すると、内部バッファのリサイズが発生せず、さらに高速になります。

4. 配列の要素を特定の文字で繋ぎたい場合

string.Join を使用してください。

自分でループを書くよりも簡潔で、バグが入り込む余地を減らせます。

注意点:null の扱いに気を付ける

文字列結合において初心者が陥りやすいのが null の扱いです。

  • + 演算子や文字列補間は、対象が null であっても空文字として扱ってくれるため、例外は発生しません。
  • しかし、変数のプロパティを直接参照して結合しようとする場合(例:obj.Name + "様")は、obj 自体が null だと NullReferenceException が発生します。

安全に結合するためには、null条件演算子 (?.)null合体演算子 (??) を併用することが重要です。

C#
string name = user?.Name ?? "Guest";
string message = $"Welcome, {name}!";

このように記述することで、予期せぬ実行時エラーを防ぎつつ、意図した通りの結合結果を得ることができます。

まとめ

C#における文字列結合には、単純なものから高度な最適化を伴うものまで、多彩なアプローチが存在します。

  • 日常的な開発では文字列補間 ($”…”)を第一選択にする。
  • ループ内や大量の結合が必要なシーンではStringBuilderを活用する。
  • リストの結合にはstring.Joinを使用する。
  • 極限のパフォーマンスが求められる場合はSpan<T>string.Createを検討する。

それぞれの特徴を理解し、「可読性」と「パフォーマンス」のバランスを考慮して最適な手法を選択することが、高品質なC#プログラムを書くための第一歩です。

この記事で紹介したテクニックを活用して、より効率的でメンテナンス性の高いコードを目指しましょう。