C#を用いたアプリケーション開発において、文字列の操作は最も頻繁に行われる処理の一つです。
その中でも「置換」は、ユーザー入力のクレンジング、ログの整形、テンプレートエンジン、データ変換など、多岐にわたるシーンで利用されます。
単純な文字列の置き換えであれば標準的なメソッドで十分ですが、パフォーマンスが求められる大規模なデータ処理や、複雑な条件を伴う置換では、適切な手法を選択しなければシステム全体のボトルネックになりかねません。
本記事では、C#における文字列置換の基本から、正規表現を用いた高度な手法、さらに最新の .NET における高速化テクニックまでを網羅的に解説します。
C#における文字列置換の基本:string.Replace メソッド
C#で最も一般的かつ直感的な置換手法は、System.String クラスに用意されている Replace メソッドを使用することです。
このメソッドは、指定した文字または文字列を別の内容に置き換えた「新しい文字列」を返します。
string.Replace の基本的な使い方
C#の文字列は不変 (immutable)であるため、Replace メソッドを呼び出しても元の文字列自体が書き換わるわけではありません。
必ず戻り値を受け取る必要がある点に注意してください。
using System;
public class ReplaceExample
{
public static void Main()
{
string text = "Hello, World! World is beautiful.";
// "World" を "C#" に置換
string result = text.Replace("World", "C#");
Console.WriteLine($"元の文字列: {text}");
Console.WriteLine($"置換後の文字列: {result}");
}
}
元の文字列: Hello, World! World is beautiful.
置換後の文字列: Hello, C#! C# is beautiful.
上記のコードでは、text.Replace("World", "C#") によって、一致するすべての箇所が置換されています。
このメソッドはデフォルトで大文字と小文字を区別するため、正確な一致が必要です。
大文字・小文字を区別せずに置換する
以前の .NET Framework では、大文字・小文字を無視して置換を行うには正規表現を使うか、文字列を一度小文字に変換するなどの工夫が必要でした。
しかし、現代的な .NET (.NET Core 2.0以降) では、StringComparison を引数に取るオーバーロードが追加されています。
using System;
public class CaseInsensitiveReplace
{
public static void Main()
{
string text = "C# is powerful. c# is fast.";
// StringComparison.OrdinalIgnoreCase を指定して大文字・小文字を無視
string result = text.Replace("c#", "F#", StringComparison.OrdinalIgnoreCase);
Console.WriteLine(result);
}
}
F# is powerful. F# is fast.
実務においては、ユーザー入力などの表記揺れを考慮する必要があるため、この StringComparison を指定する手法が非常に推奨されます。
正規表現を用いた高度な置換:Regex.Replace
単純な完全一致ではなく、「特定のパターンに一致する箇所を置換したい」場合や「複雑な条件で置換内容を動的に変更したい」場合には、System.Text.RegularExpressions.Regex クラスを使用します。
Regex.Replace の基本
正規表現を使用すると、数字のみをマスクしたり、特定のフォーマットを変換したりすることが容易になります。
using System;
using System.Text.RegularExpressions;
public class RegexReplaceExample
{
public static void Main()
{
string input = "私の電話番号は 090-1234-5678 です。";
// 数字の部分を "*" に置換するパターン
string pattern = @"\d";
string result = Regex.Replace(input, pattern, "*");
Console.WriteLine(result);
}
}
私の電話番号は ***-****-**** です。
MatchEvaluator による動的な置換
Regex.Replace の強力な機能の一つに、MatchEvaluator デリゲートを受け取るオーバーロードがあります。
これを利用すると、マッチした内容に応じて置換後の文字列をロジックで算出できます。
using System;
using System.Text.RegularExpressions;
public class DynamicReplace
{
public static void Main()
{
string input = "apple: 100yen, orange: 200yen";
// 数値を2倍にする置換処理
string result = Regex.Replace(input, @"\d+", m => {
int val = int.Parse(m.Value);
return (val * 2).ToString();
});
Console.WriteLine(result);
}
}
apple: 200yen, orange: 400yen
正規表現のパフォーマンス対策:GeneratedRegex
正規表現は柔軟ですが、実行時の解析コストがかかります。
.NET 7 以降では、Source Generator を活用した GeneratedRegex が導入されました。
これにより、コンパイル時に正規表現のエンジンを生成し、実行速度を大幅に向上させることが可能です。
using System;
using System.Text.RegularExpressions;
public partial class RegexOptimized
{
// コンパイル時に正規表現ロジックを生成
[GeneratedRegex(@"\d+")]
private static partial Regex DigitsRegex();
public void Run()
{
string result = DigitsRegex().Replace("Item123", "999");
Console.WriteLine(result);
}
}
大規模なループ内や、頻繁に呼び出される API エンドポイントなどで正規表現を使用する場合は、この GeneratedRegex の利用を優先的に検討してください。
複数の文字列を効率的に置換する手法
「Aを1に、Bを2に、Cを3に置換したい」といった、複数のキーワードを同時に置き換えるケースはよくあります。
メソッドチェーンによる逐次置換
最も単純な方法は、Replace メソッドをつなげることです。
string input = "A B C";
string result = input.Replace("A", "1").Replace("B", "2").Replace("C", "3");
しかし、この方法は置換のたびに新しい文字列インスタンスを生成するため、置換対象が多い場合や元の文字列が長い場合にメモリ効率が悪化します。
Dictionary と Regex を組み合わせた一括置換
複数のキーワードがある場合、それらを一つの正規表現パターン(OR条件)にまとめ、マッチした内容を Dictionary で引き当てて置換する方法が効率的です。
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class MultiReplace
{
public static void Main()
{
var replacements = new Dictionary<string, string>
{
{ "Apple", "りんご" },
{ "Banana", "バナナ" },
{ "Cherry", "さくらんぼ" }
};
string input = "Apple, Banana, and Cherry are fruits.";
// パターンを "Apple|Banana|Cherry" の形式で構築
string pattern = string.Join("|", replacements.Keys);
string result = Regex.Replace(input, pattern, m => replacements[m.Value]);
Console.WriteLine(result);
}
}
りんご, バナナ, and さくらんぼ are fruits.
この手法の利点は、文字列を走査するのが一回で済むことです。
大量のキーワードを個別に Replace するよりも、計算量が大幅に削減されます。
大規模データの置換と高速化:StringBuilder と Span<T>
パフォーマンスが極めて重要なシナリオでは、標準の string.Replace では不十分な場合があります。
ここでは、メモリ消費を抑えつつ高速に処理する手法を解説します。
StringBuilder.Replace の活用
同じ文字列に対して何度も置換操作を行う場合、System.Text.StringBuilder を使用するのが鉄則です。
StringBuilder は内部で可変のバッファを持つため、新しい文字列インスタンスを生成せずにその場で置換を行うことができます。
using System;
using System.Text;
public class StringBuilderExample
{
public static void Main()
{
StringBuilder sb = new StringBuilder("The quick brown fox jumps over the lazy dog.");
// StringBuilder 内で直接置換
sb.Replace("quick", "slow");
sb.Replace("brown", "white");
sb.Replace("fox", "cat");
Console.WriteLine(sb.ToString());
}
}
多くの置換処理を連続して行う場合は、string のメソッドチェーンよりも StringBuilder を優先しましょう。
Span<T> を利用した高度な最適化
.NET Core 以降、ReadOnlySpan<char> や Span<char> を活用することで、ヒープメモリへの割り当て(アロケーション)を最小限に抑えた文字列処理が可能になりました。
特に、部分的な置換や、特定条件に基づく複雑な加工を行う場合、MemoryExtensions.Replace (.NET 9以降などで拡張)や、自作のバッファ処理を組み合わせることで、ガベージコレクション (GC) の負荷を劇的に下げることができます。
| 手法 | メモリ消費 (GC負荷) | 処理速度 | 特徴 |
|---|---|---|---|
| string.Replace | 高い | 高速 | 単純な置換に最適 |
| Regex.Replace | 中〜高 | 低速 | 複雑なパターンマッチング用 |
| StringBuilder.Replace | 低い | 中〜高 | 連続した複数置換に最適 |
| Span<T> (カスタム) | 極めて低い | 最高速 | 大規模・高頻度な処理に最適 |
よくある特殊な置換ケース
改行コードの置換・統一
OSによって異なる改行コード(Windowsは \r\n、Linux/macOSは \n)を統一したい場合、以下のように記述するのが一般的です。
string input = "Line1\r\nLine2\nLine3";
// すべてを標準的な改行に統一
string normalized = input.Replace("\r\n", "\n").Replace("\r", "\n");
また、Environment.NewLine を使用することで、実行環境に合わせた改行コードへの置換も可能です。
空白や特定の文字の削除
「置換先を空文字列にする」ことで、特定の文字を削除する処理を実現できます。
string input = " Hello World ";
// スペースをすべて削除
string noSpaces = input.Replace(" ", "");
特定の文字(例えば $ や ,)をまとめて取り除きたい場合も、この手法が最もシンプルです。
まとめ
C#で文字列置換を行う際は、目的やパフォーマンス要件に応じて最適な手法を選択することが重要です。
- 単純な置換であれば、最も可読性の高い
string.Replaceを使用する。 - 大文字・小文字を無視したい場合は、
StringComparison.OrdinalIgnoreCaseを引数に指定する。 - 複雑なパターンが必要なら
Regex.Replaceを使い、パフォーマンスが求められるならGeneratedRegexを併用する。 - 大量の連続置換を行うなら、メモリ負荷を抑えるために
StringBuilderを活用する。 - 複数の異なるキーワードを一度に置換するなら、Dictionary と Regex を組み合わせる。
文字列処理はアプリケーションの基本であり、その効率はユーザー体験やインフラコストに直結します。
本記事で紹介した手法を適切に使い分け、クリーンで高速なコードを実現してください。






