変数

【C#】String Interpolation(文字列補間)の使い方と高速化テクニック

C#の文字列補間は、先頭に$を付けた文字列に変数や式を{}で直接埋め込む仕組みです。

可読性が高まり+連結やString.Formatより高速になり、複雑なロジックも簡潔に記述できます。

中括弧を文字として出したい時は{{}}でエスケープできます。

目次から探す
  1. 文字列補間の基本構文
  2. 旧来の文字列生成との比較
  3. フォーマット指定子の活用
  4. 複雑な式の埋め込みテクニック
  5. パフォーマンスの観点
  6. 補間文字列ハンドラー
  7. 生文字列リテラルとの組み合わせ
  8. verbatim文字列との併用
  9. セキュリティと安全性
  10. テストとデバッグ
  11. よくあるエラーと対処法
  12. 実践的な利用シナリオ
  13. 進化の歴史と今後の展望
  14. まとめ

文字列補間の基本構文

C#の文字列補間は、文字列の中に変数や式を直接埋め込める便利な機能です。

これにより、従来の文字列連結やString.Formatを使うよりもコードがシンプルで読みやすくなります。

ここでは、文字列補間の基本的な構文について詳しく解説します。

$プレフィックスと中括弧

文字列補間を使うには、文字列リテラルの前に$記号を付けます。

これにより、その文字列内で中括弧{}を使って変数や式を埋め込めるようになります。

例えば、以下のように書きます。

string name = "Taro";
string message = $"こんにちは、{name}さん!";
Console.WriteLine(message);

このコードでは、$が付いた文字列の中で{name}が変数nameの値に置き換わります。

こんにちは、Taroさん!

$プレフィックスがないと、{}は単なる文字列として扱われるため、補間は行われません。

必ず$を付けることが重要です。

変数の埋め込み

文字列補間の基本は、変数を中括弧で囲んで埋め込むことです。

変数の型は問わず、文字列に変換されて埋め込まれます。

以下は、複数の変数を埋め込む例です。

string firstName = "Hanako";
string lastName = "Yamada";
int age = 28;
string profile = $"名前: {lastName} {firstName}, 年齢: {age}歳";
Console.WriteLine(profile);

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

名前: Yamada Hanako, 年齢: 28歳

このように、変数を直接埋め込むことで、文字列の組み立てが非常に簡単になります。

式の埋め込み

文字列補間では、変数だけでなく式も埋め込めます。

中括弧内に計算やメソッド呼び出しなどの式を書くことが可能です。

例えば、以下のように書けます。

int x = 5;
int y = 10;
string result = $"合計は {x + y} です。";
Console.WriteLine(result);
合計は 15 です。

また、メソッド呼び出しも埋め込めます。

string ToUpperCase(string s) => s.ToUpper();
string word = "hello";
string message = $"大文字にすると {ToUpperCase(word)} です。";
Console.WriteLine(message);
大文字にすると HELLO です。

このように、式を埋め込むことで動的な文字列生成が簡単にできます。

中括弧のエスケープ

文字列補間内で中括弧{}を文字として表示したい場合は、二重にする必要があります。

つまり、{{}}と書くことで、実際の文字列に{}が出力されます。

例えば、以下のコードを見てください。

int value = 100;
string message = $"値は {{ {value} }} です。";
Console.WriteLine(message);

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

値は { 100 } です。

このように、{{}}を使うことで中括弧をエスケープし、文字列内にそのまま表示できます。

これを忘れると、コンパイルエラーや実行時エラーになることがあるので注意してください。

以上がC#の文字列補間の基本構文です。

$プレフィックスを付けて中括弧で変数や式を囲むだけで、簡単に動的な文字列を作成できます。

中括弧のエスケープも覚えておくと便利です。

旧来の文字列生成との比較

+演算子による連結

C#で文字列を結合する最もシンプルな方法は、+演算子を使うことです。

例えば、以下のように書きます。

string firstName = "Taro";
string lastName = "Yamada";
int age = 25;
string message = "名前: " + lastName + " " + firstName + ", 年齢: " + age + "歳";
Console.WriteLine(message);
名前: Yamada Taro, 年齢: 25歳

この方法は直感的で簡単ですが、複数の文字列や変数を連結するとコードが長くなり、読みづらくなります。

また、+演算子は内部的に新しい文字列を生成するため、連結が多い場合はパフォーマンスに影響が出ることがあります。

String.Formatとの違い

String.Formatメソッドは、フォーマット文字列にプレースホルダーを使い、引数で渡した値を埋め込む方法です。

例えば、先ほどの例をString.Formatで書くと以下のようになります。

string firstName = "Taro";
string lastName = "Yamada";
int age = 25;
string message = String.Format("名前: {0} {1}, 年齢: {2}歳", lastName, firstName, age);
Console.WriteLine(message);
名前: Yamada Taro, 年齢: 25歳

String.Formatは複数の値を一括で埋め込めるため、+演算子よりは可読性が向上します。

しかし、プレースホルダーの番号と引数の順序を正しく対応させる必要があり、間違えると意図しない結果になることがあります。

また、文字列の中に変数名が直接見えないため、コードの意味が分かりにくくなることもあります。

可読性と保守性の差

+演算子やString.Formatはどちらも文字列を動的に生成できますが、可読性と保守性の面で文字列補間に劣ります。

  • 可読性

文字列補間は変数名や式がそのまま文字列内に書かれるため、コードを読んだだけで何が出力されるか直感的に理解できます。

一方、+演算子は文字列と変数が分断されており、長くなると見通しが悪くなります。

String.Formatはプレースホルダーの番号と引数の対応を頭の中でマッピングする必要があり、直感的ではありません。

  • 保守性

文字列補間は変数名を直接使うため、変数名を変更するとIDEのリファクタリング機能で安全に修正できます。

String.Formatのプレースホルダーは文字列内の番号なので、引数の順序を変えたり追加したりすると番号の調整が必要でミスが起きやすいです。

+演算子は連結部分の追加や削除で括弧の位置や空白の調整が煩雑になります。

これらの理由から、C# 6.0以降は文字列補間が推奨されており、コードの見やすさと保守のしやすさが大きく向上します。

フォーマット指定子の活用

文字列補間では、変数や式の後にコロン:を付けてフォーマット指定子を記述することで、数値や日付の表示形式を細かく制御できます。

ここでは数値や日付のフォーマット指定子の使い方を詳しく説明します。

数値フォーマット

整数と小数

整数や小数の表示形式を指定するには、D(Decimal)、F(Fixed-point)、N(Number)などの標準フォーマット指定子を使います。

int number = 123;
double pi = 3.14159;
string intMessage = $"整数: {number:D5}";       // 5桁の10進数、ゼロ埋め
string floatMessage = $"小数: {pi:F2}";         // 小数点以下2桁まで表示
string numberMessage = $"カンマ区切り: {number:N0}"; // カンマ区切り、少数なし
Console.WriteLine(intMessage);
Console.WriteLine(floatMessage);
Console.WriteLine(numberMessage);
整数: 00123
小数: 3.14
カンマ区切り: 123
  • D5は整数を5桁の10進数で表示し、足りない桁はゼロで埋めます
  • F2は小数点以下2桁まで表示します
  • N0は数値をカンマ区切りで表示し、小数点以下は表示しません

通貨とパーセンテージ

通貨やパーセンテージの表示も簡単に指定できます。

Cは通貨、Pはパーセンテージのフォーマット指定子です。

decimal price = 1234.56m;
double rate = 0.075;
string currencyMessage = $"価格: {price:C}";
string percentMessage = $"利率: {rate:P1}";
Console.WriteLine(currencyMessage);
Console.WriteLine(percentMessage);

実行結果(日本のカルチャ設定の場合):

価格: ¥1,234.56
利率: 7.5 %
  • Cは通貨記号付きで小数点以下2桁まで表示します
  • P1はパーセンテージ表示で小数点以下1桁まで表示し、値は100倍されます

日付と時刻のフォーマット

日付や時刻のフォーマットも文字列補間で簡単に指定できます。

DateTime型の変数に対して、dDtTなどの標準フォーマット指定子を使います。

DateTime now = new DateTime(2024, 6, 15, 14, 30, 0);
string shortDate = $"短い日付: {now:d}";      // 2024/06/15
string longDate = $"長い日付: {now:D}";       // 2024年6月15日土曜日
string shortTime = $"短い時刻: {now:t}";      // 14:30
string longTime = $"長い時刻: {now:T}";       // 14:30:00
string customFormat = $"カスタム: {now:yyyy-MM-dd HH:mm}";
Console.WriteLine(shortDate);
Console.WriteLine(longDate);
Console.WriteLine(shortTime);
Console.WriteLine(longTime);
Console.WriteLine(customFormat);

実行結果(日本のカルチャ設定の場合):

短い日付: 2024/06/15
長い日付: 2024年6月15日土曜日
短い時刻: 14:30
長い時刻: 14:30:00
カスタム: 2024-06-15 14:30
  • dは短い日付形式、Dは長い日付形式です
  • tは短い時刻形式、Tは長い時刻形式です
  • yyyy-MM-dd HH:mmのようにカスタムフォーマットも自由に指定できます

カルチャ依存の書式設定

フォーマット指定子はカルチャ(文化圏)によって表示が変わることがあります。

例えば、通貨記号や日付の区切り文字、数字の小数点やカンマの扱いが異なります。

using System.Globalization;
decimal amount = 1234.56m;
DateTime date = new DateTime(2024, 6, 15);
CultureInfo jpCulture = new CultureInfo("ja-JP");
CultureInfo usCulture = new CultureInfo("en-US");
string jpCurrency = string.Format(jpCulture, "{0:C}", amount);
string usCurrency = string.Format(usCulture, "{0:C}", amount);
string jpDate = date.ToString("D", jpCulture);
string usDate = date.ToString("D", usCulture);
Console.WriteLine($"日本の通貨: {jpCurrency}");
Console.WriteLine($"アメリカの通貨: {usCurrency}");
Console.WriteLine($"日本の日付: {jpDate}");
Console.WriteLine($"アメリカの日付: {usDate}");
日本の通貨: ¥1,234.56
アメリカの通貨: $1,234.56
日本の日付: 2024年6月15日土曜日
アメリカの日付: Saturday, June 15, 2024

文字列補間でカルチャを指定したい場合は、FormattableStringを使い、ToStringにカルチャを渡す方法があります。

using System.Globalization;
decimal amount = 1234.56m;
FormattableString fs = $"価格は {amount:C} です。";
string jp = fs.ToString(new CultureInfo("ja-JP"));
string us = fs.ToString(new CultureInfo("en-US"));
Console.WriteLine(jp);
Console.WriteLine(us);
価格は ¥1,234.56 です。
価格は $1,234.56 です。

このように、カルチャ依存のフォーマットを使い分けることで、国や地域に合わせた表示が可能になります。

複雑な式の埋め込みテクニック

文字列補間は単純な変数の埋め込みだけでなく、複雑な式や条件分岐、メソッド呼び出しも直接埋め込めます。

ここでは三項演算子の活用、メソッド呼び出しの埋め込み、そしてNull条件演算子との組み合わせについて詳しく説明します。

三項演算子の活用

三項演算子(条件演算子)は、条件 ? 真の場合の値 : 偽の場合の値という形式で条件分岐を簡潔に書けます。

文字列補間内に埋め込むことで、動的に表示内容を切り替えられます。

int score = 75;
string result = $"テストの結果は {(score >= 60 ? "合格" : "不合格")} です。";
Console.WriteLine(result);
テストの結果は 合格 です。

この例では、scoreが60以上なら「合格」、そうでなければ「不合格」と表示されます。

三項演算子を使うことで、文字列補間内で条件に応じたメッセージを簡単に切り替えられます。

複数の条件をネストすることも可能です。

int temperature = 30;
string weather = $"今日の天気は {(temperature > 30 ? "暑い" : temperature < 10 ? "寒い" : "快適")} です。";
Console.WriteLine(weather);
今日の天気は 快適 です。

このように、三項演算子を使うと複雑な条件分岐も1行で表現でき、コードがすっきりします。

メソッド呼び出しの埋め込み

文字列補間内ではメソッド呼び出しも直接埋め込めます。

これにより、文字列の生成時に動的な処理を行うことが可能です。

string ToUpperCase(string s) => s.ToUpper();
string name = "hanako";
string message = $"名前(大文字): {ToUpperCase(name)}";
Console.WriteLine(message);
名前(大文字): HANAKO

また、標準のメソッドやプロパティも埋め込めます。

DateTime now = DateTime.Now;
string message = $"現在の時刻は {now.ToString("HH:mm:ss")} です。";
Console.WriteLine(message);

実行結果(例):

現在の時刻は 14:45:30 です。

メソッド呼び出しを埋め込むことで、文字列のフォーマットや加工を柔軟に行えます。

Null条件演算子との併用

Null条件演算子?.は、オブジェクトがnullの場合に例外を防ぎつつメンバーにアクセスできる便利な構文です。

文字列補間内でも安全に使えます。

class Person
{
    public string? Nickname { get; set; }
}
Person person = new Person { Nickname = null };
string message = $"ニックネームは {person.Nickname?.ToUpper() ?? "未設定"} です。";
Console.WriteLine(message);
ニックネームは 未設定 です。

この例では、person.Nicknamenullの場合にToUpper()を呼び出すと例外になりますが、?.を使うことでnullチェックが入り、nullならnullを返します。

さらに??演算子でnullの場合の代替文字列「未設定」を指定しています。

別の例として、ネストしたオブジェクトのプロパティに安全にアクセスする場合も有効です。

class Address
{
    public string? City { get; set; }
}
class User
{
    public Address? HomeAddress { get; set; }
}
User user = new User { HomeAddress = null };
string message = $"居住地の市区町村: {user.HomeAddress?.City ?? "不明"}";
Console.WriteLine(message);
居住地の市区町村: 不明

このように、Null条件演算子と文字列補間を組み合わせることで、null参照例外を防ぎつつ柔軟に文字列を生成できます。

パフォーマンスの観点

文字列補間はコードの可読性を大きく向上させますが、パフォーマンス面でも注目すべきポイントがあります。

ここでは、文字列補間の裏側で行われている処理やメモリ使用量、C# 10で導入されたconst補間文字列、さらにSpan<T>を活用した最適化について詳しく説明します。

裏側のString.Format呼び出し

C#の文字列補間はコンパイル時にString.Formatメソッドの呼び出しに変換されることが多いです。

例えば、以下のコード:

string name = "Taro";
int age = 30;
string message = $"名前は{name}、年齢は{age}歳です。";

はコンパイル後にほぼ次のように変換されます。

string message = String.Format("名前は{0}、年齢は{1}歳です。", name, age);

String.Formatは内部でフォーマット文字列を解析し、引数を文字列に変換して連結します。

このため、文字列補間は+演算子での連結より効率的で、複数の値をまとめて処理できるメリットがあります。

ただし、String.Formatはフォーマット文字列の解析や引数のボックス化(値型をオブジェクトに変換)を伴うため、頻繁に大量の文字列を生成する場合はパフォーマンスに影響が出ることがあります。

メモリ使用量とガベージコレクション

文字列補間で生成される文字列は不変(immutable)であるため、新しい文字列が毎回ヒープ上に作成されます。

特に複雑な補間や大量の文字列生成があると、メモリ使用量が増加し、ガベージコレクション(GC)の負荷が高まる可能性があります。

例えば、ループ内で頻繁に文字列補間を使うと、多数の一時的な文字列オブジェクトが生成され、GCが頻繁に発生してパフォーマンス低下を招くことがあります。

このような場合は、StringBuilderを使った文字列連結や、後述するSpan<T>を活用した最適化を検討すると良いでしょう。

C# 10のconst補間文字列

C# 10からは、const修飾子を付けた補間文字列がサポートされるようになりました。

これにより、コンパイル時に文字列が完全に評価され、実行時のオーバーヘッドを削減できます。

const string greeting = $"こんにちは、{"世界"}!";
Console.WriteLine(greeting);

この例では、greetingはコンパイル時に"こんにちは、世界!"に展開されるため、実行時の文字列生成コストがありません。

ただし、const補間文字列は埋め込む値がすべてコンパイル時に定数である必要があり、動的な変数や式は使えません。

静的な定数文字列を効率的に扱いたい場合に有効です。

Span<T>最適化の影響

C# 11以降や.NET 6以降のランタイムでは、文字列補間のパフォーマンスを向上させるためにSpan<T>を活用した最適化が導入されています。

Span<T>はスタック上の連続したメモリ領域を表す構造体で、ヒープ割り当てを減らし高速なメモリアクセスを可能にします。

文字列補間の内部処理でSpan<char>を使うことで、一時的な文字列バッファの割り当てを減らし、GC負荷を軽減しています。

例えば、InterpolatedStringHandlerという新しい仕組みが導入され、補間文字列の生成を効率化しています。

これにより、特にログ出力や大量の文字列生成がある場面でパフォーマンスが改善されます。

int value = 42;
string message = $"値は{value}です。";

このような単純な補間でも、InterpolatedStringHandlerが内部でSpan<char>を使い、必要なメモリを最小限に抑えています。

まとめると、Span<T>最適化は文字列補間のパフォーマンスを大幅に向上させ、特に高頻度の文字列生成で効果を発揮します。

最新のC#と.NET環境を使うことで、文字列補間の利便性とパフォーマンスを両立できます。

補間文字列ハンドラー

C# 10導入の背景

C# 10で導入された補間文字列ハンドラー(Interpolated String Handler)は、文字列補間のパフォーマンスと柔軟性を大幅に向上させるための仕組みです。

従来の文字列補間は、コンパイル時にString.Format呼び出しに変換され、実行時にすべての引数を評価し文字列を生成していました。

この方法は簡単ですが、特にログ出力のように条件によって文字列生成をスキップしたい場合に無駄な計算やメモリ割り当てが発生しやすいという課題がありました。

補間文字列ハンドラーは、補間文字列の生成処理を呼び出し側で制御できるようにし、必要に応じて文字列生成を遅延または省略できる仕組みです。

これにより、パフォーマンスの最適化やカスタム処理が可能になりました。

独自ハンドラーの実装手順

補間文字列ハンドラーは、特定のシグネチャを持つ構造体として実装します。

主に以下の要素が必要です。

  1. コンストラクター

補間文字列の長さや引数の数を受け取り、初期化処理を行います。

例: public MyInterpolatedStringHandler(int literalLength, int formattedCount)

  1. AppendLiteralメソッド

文字列リテラル部分を受け取り、処理します。

例: public void AppendLiteral(string s)

  1. AppendFormattedメソッド

埋め込まれた値を受け取り、処理します。

オーバーロードで型やフォーマット指定子を受け取ることも可能です。

例: public void AppendFormatted<T>(T value)

  1. 結果の取得メソッド

最終的な文字列や処理結果を返すメソッドを用意します。

例: public override string ToString()

以下は簡単な独自ハンドラーの例です。

using System.Text;
[System.Runtime.CompilerServices.InterpolatedStringHandler]
public ref struct MyInterpolatedStringHandler
{
    private StringBuilder builder;
    public MyInterpolatedStringHandler(int literalLength, int formattedCount)
    {
        builder = new StringBuilder(literalLength);
    }
    public void AppendLiteral(string s)
    {
        builder.Append(s);
    }
    public void AppendFormatted<T>(T value)
    {
        builder.Append(value?.ToString());
    }
    public override string ToString() => builder.ToString();
}

このハンドラーを使うには、メソッドの引数にMyInterpolatedStringHandler型を指定します。

public void LogMessage(MyInterpolatedStringHandler message)
{
    Console.WriteLine(message.ToString());
}
public void Main()
{
    LogMessage($"Hello, {"World"}!");
}

ロギングフレームワークでの応用

補間文字列ハンドラーは特にロギングフレームワークで効果を発揮します。

ログレベルによってログ出力の有無を判定し、不要な文字列生成を回避できるためです。

例えば、ログレベルがDebugの場合のみ文字列を生成し、Info以上なら生成をスキップするような実装が可能です。

public class Logger
{
    private readonly LogLevel currentLevel;
    public Logger(LogLevel level)
    {
        currentLevel = level;
    }
    public void LogDebug([System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("currentLevel")]
        DebugInterpolatedStringHandler message)
    {
        if (currentLevel <= LogLevel.Debug)
        {
            Console.WriteLine(message.ToString());
        }
    }
}
[System.Runtime.CompilerServices.InterpolatedStringHandler]
public ref struct DebugInterpolatedStringHandler
{
    private StringBuilder builder;
    private bool isEnabled;
    public DebugInterpolatedStringHandler(int literalLength, int formattedCount, LogLevel currentLevel)
    {
        isEnabled = currentLevel <= LogLevel.Debug;
        builder = isEnabled ? new StringBuilder(literalLength) : null;
    }
    public void AppendLiteral(string s)
    {
        if (isEnabled) builder.Append(s);
    }
    public void AppendFormatted<T>(T value)
    {
        if (isEnabled) builder.Append(value?.ToString());
    }
    public override string ToString() => builder?.ToString() ?? string.Empty;
}

このように、ログレベルに応じて文字列生成を抑制できるため、パフォーマンスの無駄を減らせます。

ベンチマークで確認する効果

補間文字列ハンドラーを使ったロギングと従来のString.Formatや単純な文字列補間を比較すると、特にログ出力がスキップされるケースで大きなパフォーマンス差が現れます。

  • 従来の文字列補間

ログ出力が無効でも、補間式の評価や文字列生成が行われるためCPU負荷とメモリ割り当てが発生します。

  • 補間文字列ハンドラー利用時

ログ出力が無効ならば、補間文字列の生成処理自体がスキップされ、無駄な計算やメモリ割り当てがほぼゼロになります。

ベンチマークツールで計測すると、ログ出力が無効な場合のCPU時間やGC発生回数が大幅に減少し、アプリケーション全体のパフォーマンス向上に寄与します。

この効果は特に大量のログを出力する大規模システムやリアルタイム処理で顕著です。

補間文字列ハンドラーを活用することで、文字列補間の利便性を保ちつつ、パフォーマンスを最適化できます。

生文字列リテラルとの組み合わせ

C# 11のraw string interpolations

C# 11で導入された生文字列リテラル(raw string literals)は、複数行にわたる文字列やエスケープシーケンスを気にせずに記述できる機能です。

これに加えて、文字列補間も生文字列リテラル内で使えるようになりました。

これを「raw string interpolations(生文字列補間)」と呼びます。

生文字列リテラルは、ダブルクォーテーションを3つ以上連続で囲むことで開始・終了を示します。

補間を使う場合は、先頭に$を付けて、さらに"""で囲みます。

string name = "Taro";
string message = $"""
こんにちは、{name}さん。
これはC# 11の生文字列補間の例です。
改行や"ダブルクォーテーション"もそのまま書けます。
""";
Console.WriteLine(message);
こんにちは、Taroさん。
これはC# 11の生文字列補間の例です。
改行や"ダブルクォーテーション"もそのまま書けます。

このように、複雑なエスケープ処理なしで複数行の文字列を補間付きで記述できるため、コードが非常に読みやすくなります。

複数行テンプレートでの使い所

生文字列補間は、複数行にわたるテンプレート文字列を扱う際に特に有効です。

例えば、メール本文やSQLクエリ、設定ファイルのテンプレートなど、改行やインデントを含む文字列をそのまま記述したい場合に便利です。

string userName = "Hanako";
string emailBody = $"""
件名: ご登録ありがとうございます
{userName}様
この度はご登録いただき、誠にありがとうございます。
以下の内容で登録が完了しました。

- ユーザー名: {userName}
- 登録日: {DateTime.Now:yyyy/MM/dd}

今後ともよろしくお願いいたします。
""";
Console.WriteLine(emailBody);

実行結果(例):

件名: ご登録ありがとうございます
Hanako様
この度はご登録いただき、誠にありがとうございます。
以下の内容で登録が完了しました。

- ユーザー名: Hanako
- 登録日: 2024/06/15

今後ともよろしくお願いいたします。

このように、複数行のテンプレートを自然な形で記述でき、インデントや改行もそのまま保持されるため、テンプレートの編集やメンテナンスが容易になります。

JSON・HTMLの多行埋め込み例

生文字列補間は、JSONやHTMLのような構造化されたテキストを多行で埋め込む際にも非常に役立ちます。

エスケープ文字を気にせずに記述できるため、コードがすっきりします。

JSONの例

string productName = "ノートパソコン";
int price = 120000;
string json = $"""
{{
    "商品名": "{productName}",
    "価格": {price},
    "在庫": true
}}
""";
Console.WriteLine(json);
{
    "商品名": "ノートパソコン",
    "価格": 120000,
    "在庫": true
}

JSONの中でダブルクォーテーションをエスケープする必要がなく、変数も自然に埋め込めます。

HTMLの例

string title = "C# 11の新機能";
string content = "生文字列リテラルと文字列補間の組み合わせでコードが簡潔に。";
string html = $"""
<html>
<head>
    <title>{title}</title>
</head>
<body>
    <h1>{title}</h1>
    <p>{content}</p>
</body>
</html>
""";
Console.WriteLine(html);
<html>
<head>
    <title>C# 11の新機能</title>
</head>
<body>
    <h1>C# 11の新機能</h1>
    <p>生文字列リテラルと文字列補間の組み合わせでコードが簡潔に。</p>
</body>
</html>

HTMLのタグや属性をそのまま書けるため、テンプレートエンジンを使わずに簡単なHTML生成が可能です。

生文字列補間は、複雑な文字列を扱う際のコードの可読性と保守性を大きく向上させます。

特に複数行のテンプレートや構造化テキストの埋め込みに最適な機能です。

verbatim文字列との併用

C#では、文字列リテラルの前に@を付けることでverbatim文字列を作成できます。

これにより、改行やバックスラッシュをエスケープせずにそのまま記述できるため、ファイルパスや複数行文字列の表現に便利です。

文字列補間と組み合わせる場合は、$@の両方を使いますが、書き方によって微妙な違いがあります。

$@と@$の違い

文字列補間とverbatim文字列を同時に使う場合、$@の順序はどちらでも構いません。

つまり、$@"..."@$"..."はどちらも有効です。

string name = "Taro";
string s1 = $@"こんにちは、{name}さん。";
string s2 = @$"こんにちは、{name}さん。";
Console.WriteLine(s1);
Console.WriteLine(s2);
こんにちは、Taroさん。
こんにちは、Taroさん。

両者の違いはほとんどありません。

コンパイラはどちらの順序でも同じ意味として扱います。

ただし、慣例としては$@の順で書くことが多いです。

改行とパス表記の扱い

verbatim文字列は改行をそのまま文字列に含めるため、複数行の文字列を簡単に記述できます。

また、Windowsのファイルパスのようにバックスラッシュ\をエスケープせずに書けるのが大きな利点です。

string path = $@"C:\Users\Taro\Documents\Report.txt";
string multiline = $@"これは
複数行の
文字列です。";
Console.WriteLine(path);
Console.WriteLine(multiline);
C:\Users\Taro\Documents\Report.txt
これは
複数行の
文字列です。

通常の文字列リテラルではバックスラッシュを\\と二重に書く必要がありますが、verbatim文字列ならそのまま書けます。

また、改行も文字列に含まれるため、複数行のテキストを簡単に表現できます。

バックスラッシュを含む文字列

verbatim文字列内でバックスラッシュはエスケープ不要ですが、ダブルクォーテーション"を文字として含めたい場合は、""と二重に書く必要があります。

string quote = $@"彼は言った、""こんにちは、{name}さん!""";
Console.WriteLine(quote);
彼は言った、"こんにちは、Taroさん!"

このように、verbatim文字列内でダブルクォーテーションを表現するには""と書きます。

バックスラッシュはそのまま使えるため、Windowsのパスや正規表現のパターンなどで便利です。

まとめると、文字列補間とverbatim文字列は$@または@$のどちらでも使え、改行やバックスラッシュをそのまま記述できるため、複雑な文字列の生成に適しています。

ダブルクォーテーションを含める場合は""と二重にする点に注意してください。

セキュリティと安全性

文字列補間は便利な機能ですが、セキュリティや安全性の観点から注意すべきポイントがあります。

ここでは、インジェクション対策、個人情報のマスキング、そしてフォーマット例外の防止策について詳しく説明します。

インジェクション対策

文字列補間を使ってSQLクエリやコマンドを組み立てる際に、ユーザー入力など外部からの値を直接埋め込むとSQLインジェクションのリスクが高まります。

例えば、以下のようなコードは危険です。

string userInput = "abc'; DROP TABLE Users; --";
string query = $"SELECT * FROM Users WHERE Name = '{userInput}'";
Console.WriteLine(query);

出力されるSQL文は以下のようになり、悪意のあるSQLが実行される恐れがあります。

SELECT * FROM Users WHERE Name = 'abc'; DROP TABLE Users; --'

このようなリスクを防ぐために、文字列補間で直接SQL文を組み立てるのは避け、必ずパラメータ化クエリを使うべきです。

例えば、SqlCommandのパラメータを使う方法です。

using var command = new SqlCommand("SELECT * FROM Users WHERE Name = @name", connection);
command.Parameters.AddWithValue("@name", userInput);

文字列補間はログメッセージやUI表示など安全な用途に限定し、SQLやコマンドの組み立てにはパラメータ化を徹底してください。

個人情報のマスキング

ログや画面表示に個人情報を含む場合、文字列補間で直接埋め込むと情報漏洩のリスクがあります。

例えば、メールアドレスや電話番号をそのまま表示すると問題です。

string email = "user@example.com";
string log = $"ユーザーのメールアドレス: {email}";
Console.WriteLine(log);

この場合、ログに個人情報が丸ごと記録されてしまいます。

対策としては、マスキング処理を行い、必要な部分だけを表示する方法があります。

string MaskEmail(string email)
{
    var atIndex = email.IndexOf('@');
    if (atIndex <= 1) return "***@***";
    return email.Substring(0, 1) + new string('*', atIndex - 2) + email.Substring(atIndex - 1);
}
string maskedEmail = MaskEmail(email);
string log = $"ユーザーのメールアドレス: {maskedEmail}";
Console.WriteLine(log);
ユーザーのメールアドレス: u****@example.com

このように、個人情報をマスキングしてから文字列補間に渡すことで、情報漏洩を防止できます。

フォーマット例外の防止策

文字列補間内でフォーマット指定子を使う場合、対象の値がフォーマットに適合しないと例外が発生することがあります。

例えば、数値に対して不適切なフォーマットを指定するとFormatExceptionが起きます。

int number = 123;
string message = $"値: {number:yyyy-MM-dd}"; // 数値に日付フォーマットを指定
Console.WriteLine(message);

このコードは実行時に例外を投げます。

例外を防ぐには、フォーマット指定子を使う前に型をチェックしたり、例外処理を行うことが重要です。

string SafeFormat<T>(T value, string format)
{
    try
    {
        return value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString() ?? string.Empty;
    }
    catch (FormatException)
    {
        return value?.ToString() ?? string.Empty;
    }
}
int number = 123;
string message = $"値: {SafeFormat(number, "yyyy-MM-dd")}";
Console.WriteLine(message);
値: 123

このように、フォーマット例外をキャッチして安全に文字列化することで、アプリケーションの安定性を保てます。

これらのポイントを踏まえ、文字列補間を安全に使うことが重要です。

特に外部入力を扱う場合はインジェクション対策を徹底し、個人情報は適切にマスキングし、フォーマット指定子の使用には例外処理を組み込むことをおすすめします。

テストとデバッグ

文字列補間を使ったコードは可読性が高い反面、フォーマットの誤りや意図しない値の埋め込みが起こることがあります。

ここでは、フォーマットエラーの検出テスト方法と、デバッグ時に補間文字列の内容をインラインで確認するテクニックについて詳しく説明します。

フォーマットエラー検出テスト

文字列補間でフォーマット指定子を使う場合、指定子が対象の値に適合しないとFormatExceptionが発生することがあります。

これを防ぐために、ユニットテストでフォーマットエラーを検出することが重要です。

例えば、日付フォーマットを数値に誤って適用した場合のテスト例です。

using NUnit.Framework;
using System;
public class StringInterpolationTests
{
    [Test]
    public void FormatException_ShouldBeThrown_WhenInvalidFormatIsUsed()
    {
        int number = 123;
        Assert.Throws<FormatException>(() =>
        {
            string message = $"値: {number:yyyy-MM-dd}";
        });
    }
    [Test]
    public void FormatException_ShouldNotBeThrown_WhenValidFormatIsUsed()
    {
        DateTime date = new DateTime(2024, 6, 15);
        Assert.DoesNotThrow(() =>
        {
            string message = $"日付: {date:yyyy-MM-dd}";
        });
    }
}

このように、意図的に不正なフォーマットを使った場合に例外が発生することを確認し、逆に正しいフォーマットでは例外が起きないこともテストします。

また、実際のアプリケーションでは例外を防ぐために、フォーマット前に型チェックや例外処理を行う関数を作成し、その動作をテストすることも有効です。

デバッグ時のインライン確認

デバッグ中に文字列補間の結果を確認したい場合、Visual Studioや他のIDEではウォッチウィンドウや即時ウィンドウで補間文字列を直接評価できます。

しかし、複雑な補間式の場合は一時変数に代入して中間結果を確認すると便利です。

string name = "Taro";
int age = 30;
string message = $"名前: {name}, 年齢: {age}歳";
// ブレークポイントをここに置き、messageの内容をウォッチで確認
Console.WriteLine(message);

また、補間式の中で式の結果を変数に分けておくと、どの部分がどのように評価されているかを詳細に追跡できます。

int birthYear = 1994;
int currentYear = DateTime.Now.Year;
int calculatedAge = currentYear - birthYear;
string message = $"生年: {birthYear}, 年齢: {calculatedAge}歳";

デバッグ時にcalculatedAgeの値を確認しながら、補間文字列の正確な内容を把握できます。

さらに、IDEの機能を活用して、補間文字列の中の式を選択し、即時ウィンドウで評価することも可能です。

これにより、実行時の値をリアルタイムで確認し、問題の切り分けがしやすくなります。

これらのテストとデバッグの方法を活用することで、文字列補間に関わるフォーマットエラーや値の誤りを早期に発見し、安定したコードを保つことができます。

よくあるエラーと対処法

文字列補間は便利な機能ですが、使い方を誤るとエラーや予期せぬ動作が発生することがあります。

ここでは、特に多い「中括弧の数合わせミス」「Null値による例外」「文字エンコーディングの罠」について具体例と対処法を解説します。

中括弧の数合わせミス

文字列補間では、変数や式を囲む中括弧 {} が重要な役割を果たします。

中括弧の数が合っていないとコンパイルエラーや実行時エラーになります。

例:中括弧の閉じ忘れ

string name = "Taro";
// 中括弧の閉じ忘れでコンパイルエラーになる例
string message = $"こんにちは、{name さん!";

このコードはコンパイルエラーになります。

中括弧の閉じ忘れはよくあるミスです。

例:中括弧のエスケープ忘れ

文字列内に中括弧を文字として表示したい場合は、{{}} のように二重に書く必要があります。

これを忘れるとエラーになります。

int value = 100;
// 中括弧をエスケープしないとコンパイルエラー
string message = $"値は {value} { です。"; // NG
// 正しくは
string correctMessage = $"値は {{ {value} }} です。";
Console.WriteLine(correctMessage);
値は { 100 } です。

対処法

  • 中括弧は必ずペアで書くことを意識します
  • 文字として中括弧を表示したい場合は{{}}でエスケープします
  • IDEの自動補完やシンタックスハイライトを活用し、ミスを減らす

Null値による例外

文字列補間内でnull値を扱う際に注意が必要です。

nullのままメソッド呼び出しやプロパティアクセスを行うとNullReferenceExceptionが発生します。

例:nullのままToString()呼び出し

string? name = null;
string message = $"名前は {name.ToString()} です。"; // NullReferenceException

このコードはnamenullなので例外が発生します。

対処法

  • Null条件演算子?.を使います
string? name = null;
string message = $"名前は {name?.ToString() ?? "未設定"} です。";
Console.WriteLine(message);
名前は 未設定 です。
  • 事前にnullチェックを行います
  • 文字列補間内で??演算子を使い、null時の代替文字列を指定します

文字エンコーディングの罠

文字列補間で扱う文字列のエンコーディングに注意しないと、意図しない文字化けや表示崩れが起こることがあります。

特に外部ファイルやネットワークから読み込んだ文字列を補間に使う場合に発生しやすいです。

例:ファイル読み込み時のエンコーディング不一致

// UTF-8以外のエンコーディングで保存されたファイルをUTF-8で読み込むと文字化けする
string text = File.ReadAllText("sample_sjis.txt", Encoding.UTF8);
string message = $"読み込んだテキスト: {text}";
Console.WriteLine(message);

この場合、ファイルがShift-JISで保存されているのにUTF-8で読み込むと文字化けします。

対処法

  • ファイルや外部データのエンコーディングを正しく指定して読み込みます
string text = File.ReadAllText("sample_sjis.txt", Encoding.GetEncoding("shift_jis"));
  • コンソールや出力先の文字コード設定を確認します
  • 文字列補間自体はエンコーディングに影響しませんが、元データのエンコーディング不一致が原因で文字化けが起こることを理解します

これらのエラーは初心者から経験者までよく遭遇する問題です。

中括弧のペアを正しく書くこと、null値の扱いに注意すること、そして文字エンコーディングを正しく管理することで、文字列補間を安全かつ正確に使いこなせます。

実践的な利用シナリオ

文字列補間はC#の強力な機能であり、さまざまな場面で活用できます。

ここでは、特に実務でよく使われる「ログメッセージのテンプレート」「SQLクエリ文字列の生成」「ローカライズされたUIメッセージ」の3つのシナリオを具体例とともに紹介します。

ログメッセージのテンプレート

ログ出力はアプリケーションの動作確認や障害解析に欠かせません。

文字列補間を使うと、変数の値を簡潔に埋め込んだログメッセージを作成できます。

string userName = "Taro";
int userId = 123;
DateTime loginTime = DateTime.Now;
string logMessage = $"ユーザー {userName} (ID: {userId}) が {loginTime:yyyy-MM-dd HH:mm:ss} にログインしました。";
Console.WriteLine(logMessage);

実行結果例:

ユーザー Taro (ID: 123) が 2024-06-15 14:30:00 にログインしました。

このように、日時のフォーマット指定子も使いながら、わかりやすいログメッセージを簡単に作成できます。

ただし、ログレベルによってはメッセージ生成自体を抑制したい場合もあります。

その際はC# 10以降の補間文字列ハンドラーを活用するとパフォーマンス向上が期待できます。

SQLクエリ文字列の生成

SQLクエリを文字列補間で組み立てることもありますが、直接埋め込むとSQLインジェクションのリスクがあるため注意が必要です。

安全に使うにはパラメータ化クエリを推奨しますが、動的にクエリの一部を組み立てる場合に補間が便利です。

string tableName = "Users";
string columnName = "Name";
string searchValue = "Taro";
// 動的にクエリの一部を組み立てる例(パラメータは別途設定推奨)
string query = $@"
SELECT * FROM {tableName}
WHERE {columnName} = @searchValue
ORDER BY CreatedDate DESC;
";
Console.WriteLine(query);
SELECT * FROM Users
WHERE Name = @searchValue
ORDER BY CreatedDate DESC;

この例では、テーブル名やカラム名を動的に埋め込みつつ、検索値はパラメータ化して安全性を確保しています。

文字列補間でSQLの構造を柔軟に作成しつつ、パラメータで値を渡すのがベストプラクティスです。

ローカライズされたUIメッセージ

多言語対応のアプリケーションでは、ユーザーに表示するメッセージをローカライズ(翻訳)する必要があります。

文字列補間は、翻訳済みのテンプレートに変数を埋め込む際に役立ちます。

// 例: リソースファイルなどから取得した翻訳テンプレート
string template = "こんにちは、{0}さん。あなたの残高は{1:C}です。";
string userName = "Hanako";
decimal balance = 12345.67m;
// String.Formatを使う例
string message = String.Format(template, userName, balance);
Console.WriteLine(message);

実行結果(日本のカルチャ設定の場合):

こんにちは、Hanakoさん。あなたの残高は¥12,345.67です。

C# 6以降は文字列補間を使っても同様のことができますが、リソースファイルのテンプレートはString.Format形式が多いため、補間文字列と併用する場合は注意が必要です。

一方、C# 10以降のFormattableStringを使うと、カルチャを指定して補間文字列を動的にローカライズできます。

using System.Globalization;
string userName = "Hanako";
decimal balance = 12345.67m;
FormattableString fs = $"こんにちは、{userName}さん。あなたの残高は{balance:C}です。";
string messageJa = fs.ToString(new CultureInfo("ja-JP"));
string messageEn = fs.ToString(new CultureInfo("en-US"));
Console.WriteLine(messageJa);
Console.WriteLine(messageEn);
こんにちは、Hanakoさん。あなたの残高は¥12,345.67です。
こんにちは、Hanakoさん。あなたの残高は$12,345.67です。

このように、補間文字列をカルチャに応じて変換できるため、多言語対応のUIメッセージ生成に非常に便利です。

これらのシナリオは、文字列補間の利便性を活かしつつ、安全性やパフォーマンスにも配慮した実践的な使い方の一例です。

用途に応じて適切に使い分けることで、より効率的で保守性の高いコードが書けます。

進化の歴史と今後の展望

C# 6での登場

文字列補間はC# 6.0(2015年リリース)で初めて導入されました。

それまで文字列の動的生成はString.Format+演算子による連結が主流で、コードの可読性や保守性に課題がありました。

C# 6の文字列補間は、文字列リテラルの前に$を付けて中括弧{}内に変数や式を直接埋め込めるシンプルな構文を提供し、これらの問題を大幅に改善しました。

string name = "Taro";
int age = 30;
string message = $"名前は{name}、年齢は{age}歳です。";

この導入により、コードが直感的で読みやすくなり、バグの発生も減少しました。

文字列補間はすぐにC#コミュニティで広く受け入れられ、標準的な文字列操作の手法となりました。

C# 10の拡張ポイント

C# 10(2021年リリース)では、文字列補間に関する重要な拡張が行われました。

特に注目されたのは「補間文字列ハンドラー(Interpolated String Handler)」の導入です。

従来の文字列補間は、補間部分のすべての式を評価し、String.Formatに変換されて文字列を生成していました。

これにより、例えばログ出力でログレベルが低くて出力しない場合でも、文字列生成のコストが発生してしまう問題がありました。

補間文字列ハンドラーは、補間文字列の生成を呼び出し側で制御できる仕組みで、必要に応じて文字列生成を遅延または省略できます。

これにより、パフォーマンスが大幅に向上し、特にログフレームワークなどで効果を発揮しています。

また、C# 10ではconst補間文字列もサポートされ、コンパイル時に文字列が完全に評価されるため、実行時のオーバーヘッドを削減できるようになりました。

将来の提案動向

C#の文字列補間は今後も進化が期待されています。

現在議論されている主な提案や動向には以下のようなものがあります。

  • さらなるパフォーマンス最適化

Span<char>ref structを活用した補間文字列の高速化が進められており、より低コストで文字列生成が可能になる見込みです。

  • 生文字列リテラルとの統合強化

C# 11で導入された生文字列リテラル(raw string literals)と補間の組み合わせがさらに拡充され、複雑なテンプレートや多行文字列の扱いがより簡単になる方向です。

  • 型安全な補間

補間文字列内での型チェックや補完機能の強化により、コンパイル時により多くのエラー検出が可能になる提案もあります。

  • カスタム補間ハンドラーの拡張

ユーザー定義の補間ハンドラーをより柔軟に作成できるようにするためのAPI拡充が検討されています。

これにより、ログ以外の用途でも補間文字列の生成制御が容易になります。

  • 国際化・ローカライズ対応の強化

補間文字列のカルチャ依存処理をより簡単に扱える仕組みの追加も期待されています。

これらの提案はC#の開発コミュニティや.NETのGitHubリポジトリで活発に議論されており、将来的に言語仕様やランタイムに反映される可能性があります。

文字列補間はC#の重要な機能の一つであり、今後も利便性と性能の両面で進化し続けるでしょう。

まとめ

この記事では、C#の文字列補間の基本構文からパフォーマンス最適化、複雑な式の埋め込み、補間文字列ハンドラー、最新の生文字列リテラルとの組み合わせまで幅広く解説しました。

文字列補間はコードの可読性と保守性を高めるだけでなく、C# 10以降の機能でパフォーマンス面も大きく改善されています。

実践的な利用シナリオやセキュリティ対策も押さえ、安全かつ効率的に活用するための知識が身につきます。

関連記事

Back to top button