C#の文字結合を完全解説|+演算子・文字列補間・StringBuilder・Joinの使い分けと高速化のコツ

はじめに

C#で文字列を組み立てる方法には、+演算子、文字列補間、StringBuilderString.Joinなどがあります。どの方法でも文字結合はできますが、読みやすさや処理速度、メモリ使用量、結合するデータの形によって適切な選択肢は異なります。

数個の文字列をつなげるだけなら、+演算子や文字列補間が簡単です。一方、ループ内で何度も文字列を追加する場合はStringBuilder、配列やListの要素を区切り文字付きでまとめる場合はString.Joinが適しています。

この記事では、C#の文字結合に使える主な方法を、サンプルコードとともに解説します。処理を高速化するポイントや、null、改行、数値の加算などで起こりやすい問題も確認していきましょう。

1. C#の文字列結合とは?最初に知っておきたい基本

1-1. C#で文字列を結合する主な方法

C#では、主に次の方法で文字列を結合できます。

C#
string firstName = "Taro";
string lastName = "Yamada";

// +演算子
string fullName1 = firstName + " " + lastName;

// 文字列補間
string fullName2 = $"{firstName} {lastName}";

// String.Concat
string fullName3 = string.Concat(firstName, " ", lastName);

// String.Join
string fullName4 = string.Join(" ", firstName, lastName);

いずれも結果は次のとおりです。

Taro Yamada

さらに、大量の文字列を繰り返し追加するときはStringBuilderを使用できます。

C#
using System.Text;

StringBuilder builder = new StringBuilder();
builder.Append(firstName);
builder.Append(' ');
builder.Append(lastName);

string fullName5 = builder.ToString();

それぞれの方法には得意な用途があります。単に「動作する方法」を選ぶのではなく、結合回数や元データの形式に合わせて選ぶことが重要です。

1-2. stringが変更できない「不変型」である仕組み

C#のstringは不変型、つまり一度作成された文字列の内容を後から変更できない型です。

次のコードは、変数messageの末尾に文字列を追加しているように見えます。

C#
string message = "Hello";
message += " World";

しかし、元の文字列オブジェクトである"Hello"が書き換えられているわけではありません。実際には、"Hello World"という新しい文字列が作成され、その参照がmessageに代入されます。

概念的には、次のような処理です。

C#
string message = "Hello";
string newMessage = message + " World";
message = newMessage;

この仕組みにより、stringは安全に共有しやすい一方、ループ内で何度も結合すると多くの中間文字列が作られる可能性があります。

C#
string result = "";

for (int i = 0; i < 10000; i++)
{
result += i;
}

このような処理では、結合のたびに新しい文字列が生成されます。大量の文字結合では、内容を変更できるStringBuilderの利用を検討します。

1-3. 結合回数と用途で方法を選ぶ早見表

用途適した方法
数個の固定文字列を結合する+演算子
変数や式を文章に埋め込む文字列補間
ループで大量に追加するStringBuilder
配列やListを区切り文字付きで結合するString.Join
区切り文字なしで複数の値を結合するString.Concat
書式文字列を外部から受け取るString.Format
SQL文に値を設定する文字列結合ではなくパラメーター

判断するときは、結合する文字列の数だけでなく、データがどのような形で存在しているかも確認します。

たとえば、100件のデータがすでに配列に入っているなら、StringBuilderでループするよりString.Joinのほうが簡潔です。

C#
string[] names = { "Alice", "Bob", "Carol" };
string result = string.Join(", ", names);

1-4. 迷ったときの基本方針

C#の文字結合で迷った場合は、次の方針で選ぶとよいでしょう。

少数の文字列をつなげるだけなら、+演算子を使用します。

C#
string path = directory + "/" + fileName;

文章の中に変数や計算結果を入れるなら、文字列補間を使用します。

C#
string message = $"{userName}さんの点数は{score}点です。";

配列やListの全要素をまとめるなら、String.Joinを使用します。

C#
string result = string.Join(", ", items);

ループ内で段階的に大量の文字列を作るなら、StringBuilderを使用します。

C#
StringBuilder builder = new StringBuilder();

foreach (string item in items)
{
builder.AppendLine(item);
}

最初から処理速度だけを優先するのではなく、まず読みやすく意図が伝わるコードを選ぶことが基本です。

2. +演算子・+=演算子で文字列を結合する方法

2-1. +演算子を使った基本的な文字結合

+演算子は、C#で最も基本的な文字結合の方法です。

C#
string greeting = "Hello" + " " + "World";

Console.WriteLine(greeting);

実行結果は次のとおりです。

Hello World

変数同士も結合できます。

C#
string firstName = "Taro";
string lastName = "Yamada";

string fullName = firstName + " " + lastName;

短い文字列を少数結合する場合は、処理内容が直感的に分かる+演算子が便利です。

なお、文字列リテラルだけを結合している場合、コンパイラが1つの文字列として扱えることがあります。

C#
string message = "Hello, "
+ "C# "
+ "World!";

このような定数文字列の結合は、ループ内で変数を繰り返し追加する処理とは性質が異なります。

2-2. 文字列と数値・変数を結合する方法

文字列と数値を+演算子で結合すると、数値が文字列として扱われます。

C#
string productName = "キーボード";
int price = 5000;

string message = productName + "の価格は" + price + "円です。";

Console.WriteLine(message);

実行結果は次のとおりです。

キーボードの価格は5000円です。

boolDateTimeなども結合できます。

C#
bool isActive = true;
DateTime createdAt = new DateTime(2026, 6, 20);

string status = "有効: " + isActive;
string date = "作成日: " + createdAt;

ただし、日付や小数の表示形式を細かく指定したい場合は、文字列補間やToStringの書式指定を使用するほうが読みやすくなります。

C#
decimal price = 1234.5m;
string message = $"価格は{price:N2}円です。";

2-3. +=演算子で既存の文字列に追加する方法

+=演算子を使うと、既存の文字列の末尾に新しい文字列を追加できます。

C#
string message = "Hello";
message += " ";
message += "World";

Console.WriteLine(message);

実行結果は次のとおりです。

Hello World

条件に応じて文章を追加する処理にも使用できます。

C#
string message = "処理が完了しました。";
bool hasWarning = true;

if (hasWarning)
{
message += "警告があります。";
}

ただし、stringは不変型です。+=を実行するたびに、既存の文字列が直接変更されるのではなく、新しい文字列が作成されます。

追加回数が少ない処理では問題ありませんが、大量の繰り返し処理には向いていません。

2-4. 複数行に分けて読みやすく記述する方法

長い文字列結合を1行に書くと、コードが読みにくくなります。

C#
string message = userName + "さんが" + productName + "を" + quantity + "個購入しました。合計金額は" + totalPrice + "円です。";

丸括弧で囲めば、複数行に分割できます。

C#
string message = (
userName
+ "さんが"
+ productName
+ "を"
+ quantity
+ "個購入しました。合計金額は"
+ totalPrice
+ "円です。"
);

ただし、変数を文章の中に埋め込む用途なら、文字列補間のほうが読みやすいでしょう。

C#
string message =
$"{userName}さんが{productName}を{quantity}個購入しました。"
+ $"合計金額は{totalPrice}円です。";

複数行の文章そのものを作る場合は、生文字列リテラルを使う方法もあります。

C#
string message = """
ご注文ありがとうございます。
商品の発送準備を開始しました。
発送後にメールでお知らせします。
""";

2-5. +演算子が適しているケース

+演算子は、次のようなケースに適しています。

C#
string fullName = firstName + " " + lastName;
C#
string extension = ".txt";
string fileName = baseName + extension;
C#
string label = "ID: " + id;

特に、結合する値が2~4個程度で処理が単純な場合は、+演算子で十分です。

また、固定文字列と変数を少しだけ結合する処理で、文字列補間より+演算子のほうが意図を把握しやすい場合もあります。

C#
string suffix = "_backup";
string backupName = fileName + suffix;

「数回しか結合しない処理でも、必ずStringBuilderを使うべき」ということはありません。少数の結合でStringBuilderを使用すると、かえってコード量が増えて分かりにくくなることがあります。

2-6. ループ内での+=を避けるべき理由

次のコードは、ループ内で文字列を繰り返し結合しています。

C#
string result = "";

for (int i = 0; i < 10000; i++)
{
result += i + ",";
}

stringは不変型なので、result += ...を実行するたびに新しい文字列が作られます。文字列が長くなるほど、それまでの内容を新しい領域へコピーする負担も増えます。

大量の反復では、StringBuilderを使用します。

C#
using System.Text;

StringBuilder builder = new StringBuilder();

for (int i = 0; i < 10000; i++)
{
builder.Append(i);
builder.Append(',');
}

string result = builder.ToString();

ただし、配列やListの要素を単純にカンマ区切りで結合するだけなら、String.Joinのほうが適切です。

C#
int[] numbers = Enumerable.Range(0, 10000).ToArray();
string result = string.Join(",", numbers);

3. 文字列補間で変数や式を読みやすく埋め込む方法

3-1. $を使った文字列補間の基本構文

文字列補間では、文字列リテラルの先頭に$を付け、埋め込みたい値を{}で囲みます。

C#
string name = "佐藤";
string message = $"{name}さん、こんにちは。";

Console.WriteLine(message);

実行結果は次のとおりです。

佐藤さん、こんにちは。

同じ処理を+演算子で書くと、次のようになります。

C#
string message = name + "さん、こんにちは。";

どちらも有効ですが、文章の途中に複数の変数が入る場合は文字列補間のほうが完成形を把握しやすくなります。

3-2. 複数の変数を文字列に埋め込む方法

複数の変数も、それぞれ{}で囲めば埋め込めます。

C#
string productName = "マウス";
int quantity = 3;
decimal unitPrice = 2500m;

string message =
$"{productName}を{quantity}個購入しました。"
+ $"単価は{unitPrice}円です。";

Console.WriteLine(message);

計算済みの変数を埋め込むこともできます。

C#
decimal totalPrice = quantity * unitPrice;

string message =
$"商品: {productName}、数量: {quantity}、合計: {totalPrice}円";

文字列補間では、変数名と文章の位置関係が明確になります。ログ、エラーメッセージ、画面表示用の文言などに適しています。

3-3. 数値・日付・時刻の表示形式を指定する方法

補間式の中では、コロンに続けて書式指定文字列を記述できます。

小数点以下の桁数を指定する例です。

C#
decimal price = 1234.5m;

string message = $"価格: {price:N2}円";

Console.WriteLine(message);

環境によって区切り記号は異なりますが、日本語環境では次のような結果になります。

価格: 1,234.50円

パーセント表示も可能です。

C#
double rate = 0.256;

string message = $"達成率: {rate:P1}";

日付の表示形式も指定できます。

C#
DateTime date = new DateTime(2026, 6, 20, 14, 30, 0);

string dateText = $"日付: {date:yyyy/MM/dd}";
string timeText = $"時刻: {date:HH:mm:ss}";
string fullText = $"実行日時: {date:yyyy年MM月dd日 HH時mm分}";

カルチャーに依存しない形式が必要な場合は、CultureInfo.InvariantCultureなどの使用も検討します。

C#
using System.Globalization;

decimal price = 1234.5m;

string text = price.ToString("0.00", CultureInfo.InvariantCulture);

3-4. 補間式の中で計算やメソッドを実行する方法

文字列補間の{}内には、変数だけでなく式も記述できます。

C#
int price = 1200;
int quantity = 3;

string message = $"合計金額は{price * quantity}円です。";

メソッドの戻り値も埋め込めます。

C#
string name = "taro";

string message = $"ユーザー名: {name.ToUpper()}";

条件演算子も使用できます。

C#
int score = 80;

string result = $"判定: {(score >= 60 ? "合格" : "不合格")}";

条件演算子はコロンを含むため、補間式全体を丸括弧で囲むと意図が明確になります。

複雑な処理を補間式の中に詰め込みすぎると、かえって読みにくくなります。その場合は先に変数へ代入します。

C#
decimal total = price * quantity;
string formattedTotal = total.ToString("N0");

string message = $"合計金額は{formattedTotal}円です。";

3-5. 中括弧を文字として出力する方法

通常の文字列補間で{または}を文字として出力するには、同じ記号を2つ記述します。

C#
string value = "C#";

string message = $"{{ \"language\": \"{value}\" }}";

Console.WriteLine(message);

実行結果は次のとおりです。

{ "language": "C#" }

補間に使う中括弧と、出力したい中括弧を区別する必要があります。

C#
int x = 10;

string text = $"値は{{{x}}}です。";

結果は次のようになります。

値は{10}です。

JSONやテンプレート構文など、中括弧を多用する長い文字列では、生文字列リテラルを利用すると読みやすくなる場合があります。

3-6. 逐語的文字列・生文字列リテラルと組み合わせる方法

文字列補間は、逐語的文字列リテラルと組み合わせられます。

C#
string userName = "taro";
string filePath = $@"C:\Users\{userName}\Documents\data.txt";

逐語的文字列では、バックスラッシュを\\とエスケープする必要がありません。

通常の文字列では次のように書きます。

C#
string filePath = $"C:\\Users\\{userName}\\Documents\\data.txt";

複数行の文章には、補間された生文字列リテラルも利用できます。

C#
string name = "佐藤";
int score = 92;

string message = $"""
試験結果
受験者: {name}
点数: {score}
""";

JSONのように中括弧が多い文字列では、$を2個付けることで、補間に必要な中括弧の数を変更できます。

C#
string name = "佐藤";
int score = 92;

string json = $$"""
{
"name": "{{name}}",
"score": {{score}}
}
""";

この場合、単独の{}は文字として扱われ、{{}}で囲んだ部分が補間式になります。

3-7. +演算子と文字列補間の使い分け

単純に2つの値をつなげるだけなら、+演算子でも十分に読みやすくなります。

C#
string fileName = baseName + ".txt";

文章に変数を埋め込む場合は、文字列補間が適しています。

C#
string message = $"{userName}さんのファイルは{fileName}です。";

次のような+演算子の連続は、完成する文章を把握しにくくなります。

C#
string message =
userName + "さんが" + productName + "を"
+ quantity + "個購入しました。";

文字列補間なら、より自然に読めます。

C#
string message =
$"{userName}さんが{productName}を{quantity}個購入しました。";

一方、文字列の前後に固定の接頭辞や拡張子を付けるだけなら、+演算子のほうが簡潔な場合があります。どちらか一方に統一する必要はなく、処理の意図が最も伝わる方法を選びます。

4. StringBuilderで大量の文字列を高速に結合する方法

4-1. StringBuilderとは?stringとの違い

StringBuilderは、文字列を繰り返し追加・変更するためのクラスです。

stringは不変型なので、結合するたびに新しい文字列が作成されます。対してStringBuilderは、内部に保持している文字の領域へ内容を追加できます。

C#
using System.Text;

StringBuilder builder = new StringBuilder();

builder.Append("Hello");
builder.Append(' ');
builder.Append("World");

string result = builder.ToString();

大量の繰り返し処理では、中間文字列の生成を減らせる可能性があります。

ただし、StringBuilder自体の作成や最後のToStringにもコストがあります。数個の文字列を1回結合するだけなら、+演算子や文字列補間のほうが簡潔です。

4-2. StringBuilderの宣言とusingの記述

StringBuilderSystem.Text名前空間に定義されています。

C#
using System.Text;

基本的な宣言方法は次のとおりです。

C#
StringBuilder builder = new StringBuilder();

型推論を使うこともできます。

C#
var builder = new StringBuilder();

完全修飾名を使えば、usingを記述せずに利用できます。

C#
System.Text.StringBuilder builder =
new System.Text.StringBuilder();

一般的には、ファイルの先頭にusing System.Text;を記述する方法が読みやすいでしょう。

4-3. Appendで文字列や値を追加する方法

Appendメソッドは、StringBuilderの末尾に値を追加します。

C#
StringBuilder builder = new StringBuilder();

builder.Append("商品名: ");
builder.Append("キーボード");
builder.Append(", 価格: ");
builder.Append(5000);
builder.Append("円");

AppendStringBuilder自身を返すため、メソッドチェーンでも記述できます。

C#
StringBuilder builder = new StringBuilder();

builder
.Append("商品名: ")
.Append("キーボード")
.Append(", 価格: ")
.Append(5000)
.Append("円");

文字列だけでなく、char、数値、真偽値なども追加できます。

C#
builder.Append('[');
builder.Append(123);
builder.Append(']');

1文字だけを追加するときは、"["のようなstringではなく、'['のようなcharを渡すと意図が明確です。

4-4. AppendLineで改行しながら結合する方法

AppendLineは、値を追加した後に改行を挿入します。

C#
StringBuilder builder = new StringBuilder();

builder.AppendLine("1行目");
builder.AppendLine("2行目");
builder.AppendLine("3行目");

string result = builder.ToString();

引数を指定せず、改行だけを追加することもできます。

C#
builder.AppendLine("タイトル");
builder.AppendLine();
builder.AppendLine("本文");

ループ内で複数行のテキストを作るときに便利です。

C#
string[] names = { "Alice", "Bob", "Carol" };

StringBuilder builder = new StringBuilder();

foreach (string name in names)
{
builder.AppendLine(name);
}

string result = builder.ToString();

AppendLineが追加する改行は実行環境に対応したものです。末尾にも改行が付くため、末尾改行が不要な用途ではString.Join(Environment.NewLine, values)も検討します。

4-5. AppendFormatとAppendJoinの使い方

AppendFormatを使うと、複合書式指定によって値を追加できます。

C#
StringBuilder builder = new StringBuilder();

string productName = "マウス";
decimal price = 2980m;

builder.AppendFormat(
"商品名: {0}, 価格: {1:N0}円",
productName,
price
);

カルチャーを明示するオーバーロードもあります。

C#
using System.Globalization;

builder.AppendFormat(
CultureInfo.InvariantCulture,
"Price: {0:F2}",
price
);

AppendJoinを使うと、複数の要素を区切り文字付きで追加できます。

C#
string[] names = { "Alice", "Bob", "Carol" };

StringBuilder builder = new StringBuilder();
builder.AppendJoin(", ", names);

string result = builder.ToString();

実行結果は次のとおりです。

Alice, Bob, Carol

すでにStringBuilderで文章を組み立てている途中に、コレクションの内容を追加したい場合に便利です。

C#
builder.Append("参加者: ");
builder.AppendJoin(", ", names);
builder.AppendLine();

4-6. ToStringで完成した文字列を取得する方法

StringBuilderに追加した内容を通常のstringとして取得するには、ToStringを使用します。

C#
StringBuilder builder = new StringBuilder();

builder.Append("Hello");
builder.Append(' ');
builder.Append("World");

string result = builder.ToString();

StringBuilderを引数として受け取れないAPIへ渡す場合も、ToStringで変換します。

C#
Console.WriteLine(builder.ToString());

文字列補間の中にStringBuilderを入れた場合も、最終的には文字列表現が使用されます。

C#
string message = $"結果: {builder}";

ただし、組み立ての途中で何度もToStringを呼ぶと、そのたびに完成時点の文字列が生成されます。

C#
for (int i = 0; i < 1000; i++)
{
builder.Append(i);

// 毎回stringを生成するため、必要がなければ避ける
string current = builder.ToString();
}

原則として、必要な追加処理を終えてから1回だけToStringを呼びます。

4-7. 初期容量Capacityを指定して再確保を減らす方法

StringBuilderは、内部領域が不足すると必要に応じて容量を拡張します。完成後のおおよその文字数が分かっている場合は、初期容量を指定できます。

C#
StringBuilder builder = new StringBuilder(1024);

100文字程度の行を1000件追加すると予測できる場合は、概算値を設定できます。

C#
int estimatedLength = 100 * 1000;
StringBuilder builder = new StringBuilder(estimatedLength);

既存の文字列を初期値として設定しながら、容量も指定できます。

C#
StringBuilder builder =
new StringBuilder("Header", 1024);

容量指定によって内部領域の再確保を減らせる場合がありますが、必要以上に大きな容量を指定するとメモリを余分に確保します。

初期容量は、文字数を合理的に予測でき、大量の文字列を扱う処理で検討する最適化です。

4-8. StringBuilderが効果を発揮するケースとしないケース

StringBuilderが効果を発揮しやすいのは、次のような処理です。

C#
StringBuilder builder = new StringBuilder();

for (int i = 0; i < 10000; i++)
{
builder.Append("No.");
builder.Append(i);
builder.AppendLine();
}

条件によって追加内容が変わる長い文章の生成にも適しています。

C#
StringBuilder builder = new StringBuilder();

builder.AppendLine("処理結果");

if (successCount > 0)
{
builder.Append("成功: ");
builder.AppendLine(successCount.ToString());
}

if (errorCount > 0)
{
builder.Append("失敗: ");
builder.AppendLine(errorCount.ToString());
}

一方、次のような少数の結合では、StringBuilderを使う必要性は低いでしょう。

C#
StringBuilder builder = new StringBuilder();
builder.Append(firstName);
builder.Append(' ');
builder.Append(lastName);
string fullName = builder.ToString();

この場合は、文字列補間のほうが簡潔です。

C#
string fullName = $"{firstName} {lastName}";

また、コレクションを一定の区切り文字で結合するだけなら、String.Joinを優先します。

C#
string result = string.Join(", ", names);

5. String.Joinで配列やListの文字列を区切り文字付きで結合する方法

5-1. String.Joinの基本構文

String.Joinは、複数の要素を指定した区切り文字で結合し、1つの文字列を返します。

C#
string result = string.Join(区切り文字, 要素);

たとえば、3つの文字列をカンマとスペースで結合するコードは次のとおりです。

C#
string result =
string.Join(", ", "Alice", "Bob", "Carol");

Console.WriteLine(result);

実行結果は次のようになります。

Alice, Bob, Carol

区切り文字は要素と要素の間にだけ挿入されます。末尾には付かないため、ループで区切り文字を手動追加するより安全です。

5-2. 文字列配列をカンマ区切りで結合する方法

文字列配列をカンマ区切りで結合する例です。

C#
string[] fruits =
{
"Apple",
"Banana",
"Orange"
};

string result = string.Join(",", fruits);

結果は次のとおりです。

Apple,Banana,Orange

カンマの後ろにスペースを入れる場合は、区切り文字に, を指定します。

C#
string result = string.Join(", ", fruits);

結果は次のようになります。

Apple, Banana, Orange

区切り文字を手動で追加する必要がないため、末尾に不要なカンマが残りません。

5-3. List<string>の要素を結合する方法

String.Joinには、List<string>もそのまま渡せます。

C#
List<string> names = new List<string>
{
"Alice",
"Bob",
"Carol"
};

string result = string.Join(" / ", names);

実行結果は次のとおりです。

Alice / Bob / Carol

コレクション初期化子を簡潔に書けるC#のバージョンでは、次のようにも記述できます。

C#
List<string> names = ["Alice", "Bob", "Carol"];

string result = string.Join(", ", names);

空のList<string>を渡した場合は、空文字列が返ります。

C#
List<string> names = new List<string>();

string result = string.Join(", ", names);

Console.WriteLine(result.Length); // 0

5-4. 数値配列やオブジェクトのコレクションを結合する方法

数値配列もString.Joinで結合できます。

C#
int[] numbers = { 10, 20, 30 };

string result = string.Join("-", numbers);

実行結果は次のとおりです。

10-20-30

オブジェクトのコレクションを渡した場合は、各要素の文字列表現が使用されます。ただし、業務上必要なプロパティだけを出力したい場合は、LINQのSelectで文字列へ変換する方法が明確です。

C#
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Keyboard" },
new Product { Id = 2, Name = "Mouse" }
};

string result =
string.Join(", ", products.Select(p => p.Name));

クラスの例は次のとおりです。

C#
public class Product
{
public int Id { get; set; }

public string Name { get; set; } = "";
}

オブジェクトそのものを渡すと、ToStringの実装に結果が依存します。必要な値を明示的に選択したほうが意図の分かるコードになります。

5-5. 改行・スペース・タブを区切り文字にする方法

改行区切りで結合するには、Environment.NewLineを使用します。

C#
string[] lines =
{
"1行目",
"2行目",
"3行目"
};

string result =
string.Join(Environment.NewLine, lines);

スペース区切りは次のとおりです。

C#
string result = string.Join(" ", words);

タブ区切りには\tを使用します。

C#
string result = string.Join("\t", columns);

LFだけで区切る場合は、\nを指定できます。

C#
string result = string.Join("\n", lines);

OSに応じた改行が必要ならEnvironment.NewLine、通信仕様やファイル仕様で改行コードが決まっているなら、その仕様に合わせて明示的に指定します。

5-6. LINQのSelectと組み合わせて必要な値だけ結合する方法

オブジェクトのコレクションから特定のプロパティだけを結合する場合は、Selectを使用します。

C#
List<Product> products = GetProducts();

string productNames =
string.Join(", ", products.Select(p => p.Name));

表示用の文字列へ変換してから結合することもできます。

C#
string result = string.Join(
Environment.NewLine,
products.Select(p => $"{p.Id}: {p.Name}")
);

数値の表示形式も指定できます。

C#
string prices = string.Join(
", ",
products.Select(p => p.Price.ToString("N0") + "円")
);

文字列補間を使うと、より読みやすくなります。

C#
string prices = string.Join(
", ",
products.Select(p => $"{p.Price:N0}円")
);

抽出、変換、結合という処理の流れを簡潔に記述できる点が、SelectString.Joinを組み合わせる利点です。

5-7. nullや空文字を除外して結合する方法

nullや空文字を除外するには、Whereで絞り込んでから結合します。

C#
string?[] values =
{
"Alice",
null,
"",
"Bob"
};

string result = string.Join(
", ",
values.Where(value => !string.IsNullOrEmpty(value))
);

空白だけの要素も除外する場合は、string.IsNullOrWhiteSpaceを使用します。

C#
string?[] values =
{
"Alice",
null,
"",
" ",
"Bob"
};

string result = string.Join(
", ",
values.Where(value => !string.IsNullOrWhiteSpace(value))
);

null許容参照型が有効なコードで、絞り込み後の値を明確にstringとして扱いたい場合は、Selectを追加できます。

C#
string result = string.Join(
", ",
values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!)
);

前後の空白を削除してから結合する場合は、次のように記述できます。

C#
string result = string.Join(
", ",
values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!.Trim())
);

6. String.Concat・String.Formatを使った文字列結合

6-1. String.Concatで区切り文字なしに結合する方法

String.Concatは、複数の文字列や値を区切り文字なしで結合します。

C#
string result =
string.Concat("Hello", " ", "World");

結果は次のとおりです。

Hello World

配列の全要素を、そのまま連結することもできます。

C#
string[] parts =
{
"ABC",
"123",
"XYZ"
};

string result = string.Concat(parts);

結果は次のようになります。

ABC123XYZ

文字のコレクションから文字列を作る用途にも使用できます。

C#
char[] characters = { 'C', '#', '!' };

string result = string.Concat(characters);

6-2. String.Concatと+演算子の違い

次の2つのコードは、基本的に同じ目的で使用できます。

C#
string result = firstName + " " + lastName;
C#
string result =
string.Concat(firstName, " ", lastName);

少数の値を結合する場合は、+演算子のほうが自然に読めることが多いでしょう。

一方、複数の値がコレクションとして存在し、区切り文字なしでまとめたい場合はString.Concatが便利です。

C#
IEnumerable<string> parts =
GetTextParts();

string result = string.Concat(parts);

区切り文字が必要な場合はString.Joinを使用します。

C#
string result = string.Join(", ", parts);

固定個数の結合については、コンパイラが+演算子を適切な文字列結合処理へ変換することがあります。単純なコードで、手動による細かな最適化を行う必要はありません。

6-3. String.Formatの基本構文

String.Formatは、書式文字列のプレースホルダーへ値を埋め込みます。

C#
string name = "佐藤";
int score = 90;

string message = string.Format(
"{0}さんの点数は{1}点です。",
name,
score
);

{0}には1番目の値、{1}には2番目の値が入ります。

書式も指定できます。

C#
decimal price = 1234.5m;
DateTime date = new DateTime(2026, 6, 20);

string message = string.Format(
"価格: {0:N2}円、日付: {1:yyyy/MM/dd}",
price,
date
);

同じ値を複数回利用することもできます。

C#
string message = string.Format(
"{0}さん、こんにちは。{0}さんの注文を確認しました。",
name
);

6-4. String.Formatと文字列補間の違い

String.Formatでは、書式文字列と値が離れて記述されます。

C#
string message = string.Format(
"{0}さんの点数は{1}点です。",
name,
score
);

文字列補間では、値を表示したい位置に式を記述できます。

C#
string message =
$"{name}さんの点数は{score}点です。";

通常のアプリケーションコードでは、文字列補間のほうが変数と出力位置の関係を把握しやすいでしょう。

一方、書式文字列を外部リソースや設定から取得する場合は、String.Formatが適することがあります。

C#
string template =
GetLocalizedText("ScoreMessage");

string message =
string.Format(template, name, score);

ただし、プレースホルダーの番号と引数の数が一致していないと、実行時にFormatExceptionが発生する可能性があります。

6-5. Concat・Formatを選ぶべきケース

String.Concatは、次のように区切り文字なしでコレクションを連結するときに適しています。

C#
string result = string.Concat(parts);

String.Formatは、書式文字列を変数、リソース、設定ファイルなどとして管理する場合に適しています。

C#
string template = "{0}件中{1}件を表示しています。";
string message = string.Format(template, total, displayed);

通常のコード内で文章を組み立てるなら、文字列補間を優先すると読みやすくなります。

C#
string message =
$"{total}件中{displayed}件を表示しています。";

文字列結合の方法を選ぶときは、機能の多さではなく、データの形とコードの読みやすさを基準にします。

7. 条件別に見るC#の文字結合サンプル

7-1. 文字列の間にスペースを入れて結合する

2つの文字列をスペースで結合する場合は、+演算子や文字列補間を使用できます。

C#
string firstName = "Taro";
string lastName = "Yamada";

string fullName = firstName + " " + lastName;

文字列補間では次のとおりです。

C#
string fullName = $"{firstName} {lastName}";

複数の要素をスペース区切りで結合する場合は、String.Joinが便利です。

C#
string[] words = { "C#", "string", "join" };

string result = string.Join(" ", words);

nullや空文字を除外する場合は、先に絞り込みます。

C#
string result = string.Join(
" ",
words.Where(word => !string.IsNullOrWhiteSpace(word))
);

7-2. カンマ・スラッシュ・ハイフンで区切って結合する

カンマ区切りの例です。

C#
string[] names = { "Alice", "Bob", "Carol" };

string result = string.Join(", ", names);

スラッシュ区切りの例です。

C#
string[] dateParts = { "2026", "06", "20" };

string result = string.Join("/", dateParts);

ハイフン区切りの例です。

C#
string[] codeParts = { "ABC", "123", "XYZ" };

string result = string.Join("-", codeParts);

ファイルパスを作る場合は、文字列を/\で単純結合するより、Path.Combineを使用するほうが適切です。

C#
string path =
Path.Combine(directoryPath, fileName);

見た目が区切り文字付きの文字列でも、専用APIが存在するデータでは、そのAPIを優先します。

7-3. Environment.NewLineを使って改行しながら結合する

2つの文字列を改行して結合する例です。

C#
string title = "処理結果";
string body = "正常に完了しました。";

string message =
title + Environment.NewLine + body;

文字列補間でも記述できます。

C#
string message =
$"{title}{Environment.NewLine}{body}";

複数行の配列を結合するなら、String.Joinが簡潔です。

C#
string[] lines =
{
"1行目",
"2行目",
"3行目"
};

string result =
string.Join(Environment.NewLine, lines);

段階的に大量の行を追加する場合は、StringBuilder.AppendLineを使用します。

C#
StringBuilder builder = new StringBuilder();

foreach (string line in lines)
{
builder.AppendLine(line);
}

7-4. 配列・Listの全要素を1つの文字列にする

配列を結合する基本例です。

C#
string[] colors =
{
"Red",
"Green",
"Blue"
};

string result = string.Join(", ", colors);

List<string>も同様です。

C#
List<string> colors = new List<string>
{
"Red",
"Green",
"Blue"
};

string result = string.Join(", ", colors);

区切り文字が不要ならString.Concatを使用できます。

C#
string result = string.Concat(colors);

数値のコレクションも結合できます。

C#
List<int> numbers =
new List<int> { 10, 20, 30 };

string result = string.Join(", ", numbers);

7-5. 条件に一致する要素だけを結合する

LINQのWhereを使い、条件に一致する要素だけを結合できます。

C#
int[] numbers =
{
1, 2, 3, 4, 5, 6
};

string result = string.Join(
", ",
numbers.Where(number => number % 2 == 0)
);

結果は次のとおりです。

2, 4, 6

オブジェクトのコレクションも同様です。

C#
string result = string.Join(
", ",
products
.Where(product => product.IsActive)
.Select(product => product.Name)
);

条件が複雑な場合は、判定処理をメソッドへ分離すると読みやすくなります。

C#
string result = string.Join(
", ",
products
.Where(IsDisplayTarget)
.Select(product => product.Name)
);

7-6. null・空文字・空白だけの要素を除外する

string.IsNullOrWhiteSpaceを使うと、null、空文字、空白だけの文字列をまとめて判定できます。

C#
string?[] values =
{
"Apple",
null,
"",
" ",
"Orange"
};

string result = string.Join(
", ",
values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!)
);

結果は次のとおりです。

Apple, Orange

各要素の前後にある空白も削除する場合は、Trimを組み合わせます。

C#
string result = string.Join(
", ",
values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!.Trim())
);

7-7. CSV形式の文字列を作成する

単純な値だけであれば、String.JoinでCSV風の文字列を作れます。

C#
string[] values =
{
"1001",
"Taro",
"Tokyo"
};

string csvLine = string.Join(",", values);

ただし、値にカンマ、改行、ダブルクォーテーションが含まれる場合は、適切なエスケープが必要です。

C#
static string EscapeCsvField(string? value)
{
value ??= "";

bool requiresQuotes =
value.Contains(',')
|| value.Contains('"')
|| value.Contains('\r')
|| value.Contains('\n');

if (!requiresQuotes)
{
return value;
}

return $"\"{value.Replace("\"", "\"\"")}\"";
}

使用例は次のとおりです。

C#
string[] values =
{
"1001",
"Yamada, Taro",
"He said \"Hello\""
};

string csvLine = string.Join(
",",
values.Select(EscapeCsvField)
);

実際にCSVファイルを本格的に読み書きする場合は、引用符、改行、文字コード、カルチャーなどを考慮できるCSVライブラリの使用も検討します。

7-8. ログやメッセージ文を組み立てる

簡単なメッセージは文字列補間で読みやすく作成できます。

C#
string message =
$"ユーザーID {userId} が商品ID {productId} を購入しました。";

複数行の詳細メッセージを段階的に作る場合は、StringBuilderが便利です。

C#
StringBuilder builder = new StringBuilder();

builder.AppendLine("注文処理に失敗しました。");
builder.Append("注文ID: ");
builder.AppendLine(orderId.ToString());
builder.Append("理由: ");
builder.AppendLine(errorMessage);

string message = builder.ToString();

ただし、ロギングライブラリでは、文字列を先に結合せず、構造化ログのプレースホルダーを利用できる場合があります。

C#
logger.LogInformation(
"User {UserId} purchased product {ProductId}",
userId,
productId
);

ログレベルが無効なときの不要な文字列生成を減らせるほか、ユーザーIDや商品IDを検索可能な属性として保持できることがあります。

7-9. SQL文の文字列結合を避けてパラメーターを使う理由

ユーザー入力をSQL文へ直接結合してはいけません。

C#
string sql =
"SELECT * FROM Users WHERE Name = '"
+ userInput
+ "'";

この方法には、SQLインジェクションの危険があります。入力値に引用符やSQL構文が含まれると、意図しないSQLが実行される可能性があります。

値はSQLパラメーターとして渡します。

C#
string sql =
"SELECT * FROM Users WHERE Name = @name";

using SqlCommand command =
new SqlCommand(sql, connection);

command.Parameters.Add(
"@name",
SqlDbType.NVarChar,
100
).Value = userInput;

数値も同様です。

C#
string sql =
"SELECT * FROM Products WHERE Price >= @minPrice";

using SqlCommand command =
new SqlCommand(sql, connection);

command.Parameters.Add(
"@minPrice",
SqlDbType.Decimal
).Value = minPrice;

パラメーターを使うことで、入力値をSQL構文ではなくデータとして扱えます。型や長さを明示しやすく、文字列の引用符を手作業でエスケープする必要もありません。

8. C#の文字列結合を高速化するポイント

8-1. 文字列結合でメモリ割り当てが発生する仕組み

stringは不変型であるため、実行時の文字列を結合すると、通常は結果を格納する新しい文字列が必要になります。

C#
string first = "Hello";
string second = "World";

string result = first + " " + second;

結合元のfirstsecondが書き換えられるのではなく、結合結果を保持するresult用の文字列が作られます。

一度の処理であれば問題になることはほとんどありません。しかし、長くなり続ける文字列へループで追加すると、以前の内容を新しい文字列へ何度もコピーする可能性があります。

C#
string result = "";

foreach (string item in items)
{
result += item;
}

処理回数や完成する文字列が大きい場合は、メモリ割り当てとコピーの量が増えます。

8-2. 1回だけの結合では+演算子でも問題がない理由

数個の値を1回結合するだけなら、+演算子は十分に実用的です。

C#
string fullName =
firstName + " " + lastName;

StringBuilderへ置き換えると、コード量が増えます。

C#
StringBuilder builder = new StringBuilder();
builder.Append(firstName);
builder.Append(' ');
builder.Append(lastName);

string fullName = builder.ToString();

この程度の処理では、StringBuilderオブジェクトの生成やToStringも必要です。必ずしも高速化になるとは限りません。

さらに、コンパイラやランタイムは、固定個数の文字列結合を効率的な処理へ変換できる場合があります。少数の結合では、読みやすい+演算子や文字列補間を選びます。

8-3. ループ処理ではStringBuilderを検討する

繰り返し回数が多く、同じ結果へ文字列を追加し続ける場合はStringBuilderを検討します。

C#
StringBuilder builder = new StringBuilder();

foreach (Product product in products)
{
builder.Append(product.Id);
builder.Append(": ");
builder.AppendLine(product.Name);
}

string result = builder.ToString();

ただし、「ループがある」という理由だけで必ずStringBuilderが必要になるわけではありません。

各要素を個別に処理し、結果を1つの文字列へ蓄積しないなら、文字列補間で問題ありません。

C#
foreach (Product product in products)
{
Console.WriteLine(
$"{product.Id}: {product.Name}"
);
}

また、コレクション全体を一定の区切り文字でまとめるだけならString.Joinが適しています。

8-4. コレクションの結合ではString.Joinを優先する

次のコードは、手動でカンマ区切りの文字列を作っています。

C#
StringBuilder builder = new StringBuilder();

for (int i = 0; i < names.Count; i++)
{
if (i > 0)
{
builder.Append(", ");
}

builder.Append(names[i]);
}

string result = builder.ToString();

この処理はString.Joinで簡潔に記述できます。

C#
string result = string.Join(", ", names);

String.Joinなら、区切り文字を要素間にだけ挿入する意図が明確です。先頭や末尾の区切り文字を個別に処理する必要もありません。

抽出や変換が必要な場合も、LINQと組み合わせられます。

C#
string result = string.Join(
", ",
products.Select(product => product.Name)
);

8-5. 不要なToStringや中間文字列の生成を減らす

値をStringBuilder.Appendへ渡すとき、必ずしも先にToStringする必要はありません。

C#
int count = 100;

// 不要な中間文字列を作る可能性がある
builder.Append(count.ToString());

数値を直接渡せます。

C#
builder.Append(count);

文字列補間の結果を作ってからAppendするより、値を分けて追加したほうが中間文字列を減らせる場合があります。

C#
builder.Append($"ID: {id}, Name: {name}");

大量に繰り返す処理では、次のように分けられます。

C#
builder
.Append("ID: ")
.Append(id)
.Append(", Name: ")
.Append(name);

ただし、少量の処理で細かく分割すると可読性が下がることがあります。実際に性能上の問題が確認できる部分へ限定して最適化します。

8-6. StringBuilderの容量を事前に確保する

完成する文字数を予測できる場合は、初期容量を指定します。

C#
int estimatedLength =
products.Count * 50;

StringBuilder builder =
new StringBuilder(estimatedLength);

1行あたり平均50文字で、1000件なら約5万文字というように概算できます。

C#
StringBuilder builder =
new StringBuilder(50_000);

容量が不足すると内部領域が拡張されますが、初期容量を適切に設定すれば再確保の回数を減らせる可能性があります。

ただし、過大な容量を設定すると、その分だけ不要なメモリを確保します。推測だけで極端な値を指定せず、データ量やベンチマークに基づいて調整します。

8-7. ベンチマークで処理時間とメモリ使用量を比較する

文字列結合の性能は、結合回数、各文字列の長さ、実行環境、コンパイラ、ランタイムによって変わります。

単純なStopwatchでもおおよその時間は確認できます。

C#
using System.Diagnostics;
using System.Text;

Stopwatch stopwatch = Stopwatch.StartNew();

StringBuilder builder = new StringBuilder();

for (int i = 0; i < 100000; i++)
{
builder.Append(i);
}

string result = builder.ToString();

stopwatch.Stop();

Console.WriteLine(stopwatch.ElapsedMilliseconds);

ただし、正確な比較では次の点を考慮する必要があります。

  • JITコンパイルの影響

  • ウォームアップ

  • ガベージコレクション

  • 複数回実行によるばらつき

  • デバッグビルドとリリースビルドの違い

  • 処理結果が最適化で除去されない工夫

  • 実行時間だけでなく割り当てメモリ量

本格的な計測では、BenchmarkDotNetのようなベンチマーク用ライブラリを利用すると、複数の方式を比較しやすくなります。

8-8. 可読性を犠牲にした過剰な最適化を避ける

次のコードは簡潔で、意図も明確です。

C#
string message =
$"{userName}さんの注文金額は{totalPrice:N0}円です。";

わずかなメモリ割り当てを避ける目的で細かく分割すると、処理内容を把握しにくくなることがあります。

C#
StringBuilder builder = new StringBuilder();
builder.Append(userName);
builder.Append("さんの注文金額は");
builder.Append(totalPrice.ToString("N0"));
builder.Append("円です。");

このコードが大量ループの内部にあり、計測によって問題が確認されているなら最適化に意味があります。しかし、1回しか実行されない画面メッセージであれば、文字列補間のほうが保守しやすいでしょう。

最適化は、推測ではなく計測結果に基づいて行います。

9. C#の文字結合で起こりやすいエラーと注意点

9-1. nullを結合したときの結果

文字列とnull+演算子で結合すると、null部分は空文字列のように扱われます。

C#
string? name = null;

string message = "名前: " + name;

Console.WriteLine(message);

結果は次のとおりです。

名前:

文字列補間でも、通常は値がない部分が空として出力されます。

C#
string message = $"名前: {name}";

String.Joinnull要素を含むコレクションを渡した場合も、その位置は空文字列として扱われるため、区切り文字が連続することがあります。

C#
string?[] values =
{
"A",
null,
"C"
};

string result = string.Join(",", values);

結果は次のようになります。

A,,C

nullを除外したい場合は、事前に絞り込みます。

C#
string result = string.Join(
",",
values.Where(value => value is not null)
);

9-2. 数値の加算と文字列結合の優先順位

+演算子は、数値同士では加算、文字列を含む場合は文字列結合として動作します。

C#
string result = "合計: " + 10 + 20;

演算は左から進むため、結果は次のようになります。

合計: 1020

10 + 20を先に計算したい場合は、丸括弧で囲みます。

C#
string result =
"合計: " + (10 + 20);

結果は次のとおりです。

合計: 30

文字列補間では、計算式をそのまま埋め込めます。

C#
string result = $"合計: {10 + 20}";

数値計算と文字列結合が混在する場合は、丸括弧または文字列補間を使い、評価順序を明確にします。

9-3. 区切り文字が末尾に付いてしまう問題

ループで区切り文字を追加すると、末尾に不要なカンマが付くことがあります。

C#
string result = "";

foreach (string name in names)
{
result += name + ",";
}

結果は次のようになります。

Alice,Bob,Carol,

末尾を削除する処理を追加するより、String.Joinを使用します。

C#
string result =
string.Join(",", names);

StringBuilderで複雑な処理を行う場合は、最初の要素以外の前に区切り文字を付ける方法があります。

C#
StringBuilder builder = new StringBuilder();

bool isFirst = true;

foreach (string name in names)
{
if (!isFirst)
{
builder.Append(',');
}

builder.Append(name);
isFirst = false;
}

単純なコレクション結合では、String.Joinのほうが短く安全です。

9-4. 改行コードによるOSごとの差

一般に、WindowsではCRLF、Linux系環境ではLFが改行として使われます。

実行環境に合った改行を追加するなら、Environment.NewLineを使用します。

C#
string text =
"1行目"
+ Environment.NewLine
+ "2行目";

StringBuilder.AppendLineも、環境に応じた改行を追加します。

C#
builder.AppendLine("1行目");
builder.AppendLine("2行目");

ただし、HTTP、CSV、外部システムとの連携、特定のファイル仕様などでは、改行コードが明示的に決められている場合があります。

その場合は環境任せにせず、仕様に従います。

C#
string text = string.Join("\n", lines);

9-5. 文字コードと文字列結合を混同しない

C#のstringを結合する処理と、ファイルへ保存するときの文字コードは別の問題です。

C#
string content =
string.Join(Environment.NewLine, lines);

この時点では文字列を組み立てているだけです。UTF-8やShift_JISなどの指定は、文字列をバイト列やファイルへ変換するときに行います。

C#
File.WriteAllText(
filePath,
content,
Encoding.UTF8
);

バイト配列へ変換する場合も同様です。

C#
byte[] bytes =
Encoding.UTF8.GetBytes(content);

文字化けが発生した場合、原因は文字列結合ではなく、書き込み側と読み込み側の文字コードが一致していないことにあるかもしれません。

9-6. StringBuilderを使い回す際のClear忘れ

同じStringBuilderを使って複数の文字列を作る場合は、前回の内容を消す必要があります。

C#
StringBuilder builder = new StringBuilder();

builder.Append("1件目");
string first = builder.ToString();

builder.Clear();

builder.Append("2件目");
string second = builder.ToString();

Clearを忘れると、前回の内容が残ります。

C#
builder.Append("1件目");
string first = builder.ToString();

builder.Append("2件目");
string second = builder.ToString();

この場合、secondは次のようになります。

1件目2件目

Clearを実行しても、内部容量が直ちに初期状態へ戻るとは限りません。非常に大きな文字列を一度作ったStringBuilderを長期間使い回すと、大きな容量を保持し続ける可能性があります。

再利用が常に最善とは限らないため、用途や文字列サイズに応じて新しいインスタンスを作成します。

9-7. IEnumerableを複数回評価してしまう問題

LINQで作ったIEnumerable<T>は、列挙されるたびに処理が実行される場合があります。

C#
IEnumerable<string> names =
users
.Where(user => user.IsActive)
.Select(user => user.Name);

int count = names.Count();
string result = string.Join(", ", names);

このコードでは、CountString.Joinによって、元の処理が2回評価される可能性があります。

評価を1回にしたい場合は、配列やリストへ変換します。

C#
string[] names =
users
.Where(user => user.IsActive)
.Select(user => user.Name)
.ToArray();

int count = names.Length;
string result = string.Join(", ", names);

データベースアクセスを伴うLINQでは、複数回の評価が複数回のクエリ実行につながる可能性もあります。列挙元の性質を確認し、必要に応じて結果を具体化します。

10. C#の文字結合に関するよくある質問

10-1. +演算子と文字列補間はどちらが速い?

少数の値を一度だけ結合する処理では、+演算子と文字列補間の速度差を過度に気にする必要はありません。

C#
string message =
"名前: " + name + ", 年齢: " + age;
C#
string message =
$"名前: {name}, 年齢: {age}";

コンパイラやランタイムによって効率的な処理へ変換される場合があり、型や書式指定によっても結果は変わります。

通常は、完成する文章を読みやすい文字列補間を選びます。性能が重要な大量ループでは、実際の処理内容をベンチマークして判断します。

10-2. StringBuilderは何回以上の結合で使うべき?

「5回以上なら必ずStringBuilder」のような絶対的な基準はありません。

判断に影響する要素は次のとおりです。

  • 結合回数

  • 各文字列の長さ

  • 完成する文字列の大きさ

  • ループ処理かどうか

  • コレクションを一定の区切りでまとめる処理か

  • 実行頻度

  • 利用している.NETランタイム

数回の結合なら、+演算子や文字列補間を使用します。

C#
string message =
$"{firstName} {lastName}";

数百回、数千回と同じ結果へ追加し続けるなら、StringBuilderを検討します。

C#
StringBuilder builder = new StringBuilder();

foreach (string item in items)
{
builder.Append(item);
}

ただし、一定の区切り文字でコレクションをまとめるだけならString.Joinが第一候補です。

10-3. String.JoinとStringBuilderはどちらを使うべき?

配列やListの要素を同じ区切り文字で結合するなら、String.Joinを使用します。

C#
string result =
string.Join(", ", names);

条件分岐をしながら異なる形式の文字列を段階的に追加するなら、StringBuilderが適しています。

C#
StringBuilder builder = new StringBuilder();

foreach (Product product in products)
{
builder.Append(product.Id);
builder.Append(": ");
builder.Append(product.Name);

if (!product.IsActive)
{
builder.Append(" [無効]");
}

builder.AppendLine();
}

単純な要素間の区切りはString.Join、複雑な文章構築や大量の段階的追加はStringBuilderと考えると選びやすくなります。

10-4. Listの要素を改行区切りで結合するには?

String.Joinの区切り文字にEnvironment.NewLineを指定します。

C#
List<string> lines = new List<string>
{
"1行目",
"2行目",
"3行目"
};

string result =
string.Join(Environment.NewLine, lines);

末尾に改行は付きません。各行の末尾にも改行が必要な場合は、StringBuilder.AppendLineを利用できます。

C#
StringBuilder builder = new StringBuilder();

foreach (string line in lines)
{
builder.AppendLine(line);
}

string result = builder.ToString();

10-5. nullや空文字を無視して結合するには?

Wherestring.IsNullOrWhiteSpaceを組み合わせます。

C#
string?[] values =
{
"A",
null,
"",
" ",
"B"
};

string result = string.Join(
", ",
values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!)
);

結果は次のとおりです。

A, B

空文字だけを除外し、空白だけの文字列は残したい場合は、string.IsNullOrEmptyを使用します。

C#
values.Where(
value => !string.IsNullOrEmpty(value)
);

10-6. 文字列の末尾に区切り文字を付けずに結合するには?

配列やListを結合する場合は、String.Joinを使用します。

C#
string result =
string.Join(",", values);

String.Joinは、要素と要素の間にだけ区切り文字を入れるため、末尾には付きません。

複雑な条件でStringBuilderを使う場合は、2件目以降の要素の前に区切り文字を追加します。

C#
StringBuilder builder = new StringBuilder();

foreach (string value in values)
{
if (builder.Length > 0)
{
builder.Append(',');
}

builder.Append(value);
}

ただし、空文字を追加する可能性がある処理では、builder.Length > 0が要素数の判定として適切か確認してください。必要ならbool isFirstやカウンターを使用します。

10-7. charとstringを結合するには?

charstring+演算子で結合できます。

C#
char prefix = '#';
string number = "100";

string result = prefix + number;

結果は次のとおりです。

#100

StringBuilderにもcharを直接追加できます。

C#
StringBuilder builder = new StringBuilder();

builder.Append('[');
builder.Append("value");
builder.Append(']');

複数のcharから文字列を作る場合は、char[]stringのコンストラクターへ渡せます。

C#
char[] chars =
{
'C',
'#'
};

string result = new string(chars);

char + charは文字列結合ではなく、数値的な加算として扱われる可能性があるため注意が必要です。

C#
int result = 'A' + 'B';

2文字を文字列として結合したい場合は、片方を文字列にするか、文字列補間を使用します。

C#
string result1 = 'A' + "B";
string result2 = $"{'A'}{'B'}";

10-8. 最も読みやすく保守しやすい結合方法は?

用途ごとに最も意図が伝わる方法を選ぶことが重要です。

文章へ変数を埋め込む場合は、文字列補間が読みやすいでしょう。

C#
string message =
$"{name}さんの注文金額は{total:N0}円です。";

少数の文字列を単純につなぐ場合は、+演算子が簡潔です。

C#
string fileName =
baseName + ".txt";

コレクションを区切り文字付きで結合する場合は、String.Joinが適しています。

C#
string result =
string.Join(", ", names);

大量の段階的な追加には、StringBuilderを使用します。

C#
StringBuilder builder = new StringBuilder();

foreach (string line in lines)
{
builder.AppendLine(line);
}

コードレビューをしやすくするため、プロジェクト内で基本方針を決めることも有効です。ただし、すべての文字列結合を1つの方法に統一するのではなく、用途に応じて使い分けます。

まとめ

C#の文字結合には複数の方法があり、それぞれ適した用途があります。

少数の文字列を単純につなげる場合は、+演算子が簡潔です。

C#
string fullName =
firstName + " " + lastName;

変数や式を文章へ埋め込む場合は、文字列補間を使用すると完成形を読み取りやすくなります。

C#
string message =
$"{name}さんの点数は{score}点です。";

配列やListを区切り文字付きで結合する場合は、String.Joinが適しています。

C#
string result =
string.Join(", ", values);

ループ内で大量の文字列を段階的に追加する場合は、StringBuilderを検討します。

C#
StringBuilder builder = new StringBuilder();

foreach (string value in values)
{
builder.AppendLine(value);
}

また、文字列結合では、null、数値の加算順序、末尾の区切り文字、改行コード、中間文字列の生成などに注意が必要です。

性能を高めることは重要ですが、数回の結合にまで複雑な最適化を適用する必要はありません。まずは処理の目的が伝わる方法を選び、大量データや高頻度処理で問題が確認された場合に、ベンチマーク結果を基にStringBuilderや容量指定などを検討しましょう。