はじめに 「正規表現置換」は“パターンで探して、一気に書き換える”技
一致判定は「合っているかどうか」、抽出は「取り出す」でした。
正規表現置換は、そのさらに一歩先——「合っているところを、別の形に書き換える」ための道具です。
電話番号の一部をマスクする。
日付のフォーマットを yyyy/MM/dd に統一する。
ログ中の URL にリンクを付ける。
古いコードのパターンを一括で新しい書き方に変える。
こういう「パターンに合うところを全部まとめて変えたい」というときに、Regex.Replace が本領発揮します。
ここでは、初心者向けに、
Regex.Replace の基本
キャプチャグループと $1/${name} を使った“並べ替え・整形”MatchEvaluator を使った“動的な置換”
null 安全なユーティリティ化と、実務での典型パターン
を、例題付きでかみ砕いて説明していきます。
Regex.Replace の基本を押さえる
まずは「単純なパターン置換」
一番シンプルな形は、「パターンに合うところを、固定の文字列に置き換える」です。
using System;
using System.Text.RegularExpressions;
string text = "ABC-123-XYZ";
string result = Regex.Replace(text, @"\d", "*");
Console.WriteLine(result); // ABC-***-XYZ
C#ここでは、\d(数字 1 文字)にマッチしたところを、すべて "*" に置き換えています。Regex.Replace(入力文字列, パターン, 置換文字列) という形が基本です。
String.Replace との違いは、「単なる文字列」ではなく「正規表現パターン」で検索できることです。
つまり、「数字全部」「空白の連続」「特定の形式の日付」など、柔軟な条件で置換できます。
「全部」か「一部」かはパターン次第
Regex.Replace は、パターンにマッチした“すべての部分”を対象にします。
どこを変えるかは、パターンの書き方次第です。
例えば、先頭の数字だけを変えたいなら、こう書きます。
string text = "123-456-789";
string result = Regex.Replace(text, @"^\d+", "XXX");
Console.WriteLine(result); // XXX-456-789
C#^ は「先頭」を意味するので、「先頭の数字の連続だけ」が置換対象になります。
「どこが対象になるか」は、正規表現パターンの設計そのものです。
キャプチャグループ+置換トークンで“並べ替え・整形”をする
などの「置換トークン」とは何か
Regex.Replace の真価は、「マッチした文字列をそのまま捨てて別の固定文字列にする」のではなく、
「マッチした中身を再利用しながら並べ替えたり、フォーマットを変えたりできる」ところにあります。 af-e.net
そのために使うのが、キャプチャグループと「置換トークン」です。
例えば、yyyy-mm-dd を dd/mm/yyyy に並べ替えたいとします。
string text = "2025-02-01";
string result = Regex.Replace(
text,
@"(\d{4})-(\d{2})-(\d{2})",
"$3/$2/$1");
Console.WriteLine(result); // 01/02/2025
C#ここでのポイントはこうです。
(\d{4}) … 第1グループ(年)(\d{2}) … 第2グループ(月)(\d{2}) … 第3グループ(日)
置換文字列の $1, $2, $3 は、それぞれ「対応するグループにマッチした文字列」を意味します。
つまり、「年-月-日」を「日/月/年」に並べ替えているわけです。
名前付きグループ+${name} でさらに読みやすく
グループが増えてくると、「$1 が年で $2 が月で…」というのが分かりづらくなります。
そこで、名前付きグループと ${name} を使うと、意図がはっきりします。
string text = "2025-02-01";
string result = Regex.Replace(
text,
@"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})",
"${day}/${month}/${year}");
Console.WriteLine(result); // 01/02/2025
C#(?<year>...) のように書くと、そのグループに year という名前が付きます。
置換側では ${year}, ${month}, ${day} と書いて再利用できます。
「何をどう並べ替えているのか」が、コードから一目で分かるので、
業務コードでは名前付きグループ+${name} を強くおすすめします。
実務でよくある「マスク処理」を Regex.Replace で書く
電話番号の一部を伏せ字にする
個人情報マスクは、正規表現置換の典型ユースケースです。
例えば、「電話番号の下4桁を **** にする」処理を考えます。
using System.Text.RegularExpressions;
string text = "電話番号: 090-1234-5678";
string result = Regex.Replace(
text,
@"(\d{2,4}-\d{2,4}-)\d{4}",
"$1****");
Console.WriteLine(result); // 電話番号: 090-1234-****
C#ここでは、
(\d{2,4}-\d{2,4}-) … 市外局番+市内局番+ハイフンまでを第1グループにキャプチャ\d{4} … 下4桁
置換側で $1**** とすることで、「前半はそのまま」「後半だけ伏せ字」にしています。
メールアドレスのローカル部をマスクする
メールアドレスの @ より前を一部だけ残してマスクする例です。
string text = "連絡先: user.name@example.com";
string result = Regex.Replace(
text,
@"([0-9A-Za-z._%+-])([0-9A-Za-z._%+-]*)(@[^@\s]+)",
"$1***$3");
Console.WriteLine(result); // 連絡先: u***@example.com
C#ここでは、
第1グループ … 先頭1文字
第2グループ … 残りのローカル部
第3グループ … @ 以降
という分解をして、置換側で $1***$3 とすることで、
「先頭1文字+***+ドメイン」という形にしています。
MatchEvaluator を使った“動的な置換”
置換後の文字列を「コードで決めたい」場合
ここまでの例は、「置換後の文字列が固定」でした。
しかし、業務では「マッチした数値に計算をかけて書き換える」「大文字・小文字を変換する」など、
“マッチした内容に応じて置換結果を変えたい”ことがよくあります。
そのときに使うのが、MatchEvaluator(デリゲート)を受け取るオーバーロードです。
string text = "価格: 100, 200, 300";
string result = Regex.Replace(
text,
@"\d+",
m =>
{
int value = int.Parse(m.Value);
int withTax = (int)(value * 1.1);
return withTax.ToString();
});
Console.WriteLine(result); // 価格: 110, 220, 330
C#ここでは、
@"\d+" で数字を全部見つけるm.Value でマッチした数字文字列を取り出す
10% 加算して文字列に戻す
という処理を、ラムダ式の中で行っています。
Regex.Replace(入力, パターン, MatchEvaluator) という形を覚えておくと、
「マッチした部分を、コードで自由に加工して置き換える」ことができます。
大文字・小文字を変換する例
例えば、「{{name}} のようなプレースホルダを、全部大文字に変換した中身で置き換える」例です。
string text = "Hello, {{userName}} and {{adminName}}.";
string result = Regex.Replace(
text,
@"\{\{(?<name>[A-Za-z0-9_]+)\}\}",
m =>
{
string name = m.Groups["name"].Value;
return name.ToUpperInvariant();
});
Console.WriteLine(result); // Hello, USERNAME and ADMINNAME.
C#ここでは、{{...}} の中身を名前付きグループ name で取り出し、
それを大文字にして返しています。
null 安全な「正規表現置換ユーティリティ」を作る
毎回 null チェックを書くのをやめる
Regex.Replace は、入力文字列が null だと ArgumentNullException になります。
業務コードでは「null かもしれない文字列」を扱うことが多いので、
毎回こう書くのはつらいです。
if (text is null)
{
return text;
}
return Regex.Replace(text, pattern, replacement);
C#そこで、「null のときはそのまま返す」ユーティリティを 1 個用意しておくと楽になります。
using System;
using System.Text.RegularExpressions;
public static class RegexReplaceUtil
{
public static string? ReplaceSafe(
string? value,
string pattern,
string replacement,
RegexOptions options = RegexOptions.None)
{
if (value is null)
{
return null;
}
return Regex.Replace(value, pattern, replacement, options);
}
public static string? ReplaceSafe(
string? value,
string pattern,
MatchEvaluator evaluator,
RegexOptions options = RegexOptions.None)
{
if (value is null)
{
return null;
}
return Regex.Replace(value, pattern, evaluator, options);
}
}
C#使い方の例です。
string? text = GetFromConfig(); // null かもしれない
string? masked = RegexReplaceUtil.ReplaceSafe(
text,
@"(\d{2,4}-\d{2,4}-)\d{4}",
"$1****");
C#「null のときは null のまま」「それ以外は普通に置換」という挙動が、
メソッド名からも分かるようになります。
実務での設計ポイント 「パターンと意図をユーティリティに閉じ込める」
パターンと置換文字列をあちこちにベタ書きしない
悪い例を挙げると、こんな感じです。
text = Regex.Replace(text, @"(\d{2,4}-\d{2,4}-)\d{4}", "$1****");
// 別の場所でも同じパターン
log = Regex.Replace(log, @"(\d{2,4}-\d{2,4}-)\d{4}", "$1****");
C#同じパターンと置換文字列が、コードのあちこちに散らばっている状態です。
こうなると、
どこを直せばいいか分からない
一部だけ修正漏れが出る
そもそも何をしたい置換なのか読み取りづらい
という問題が出てきます。
「意味のある名前のメソッド」にしてしまう
例えば、「電話番号マスク」という意図を持ったユーティリティにしてしまうと、
呼び出し側はかなり読みやすくなります。
public static class MaskUtil
{
private static readonly Regex PhoneRegex =
new Regex(@"(\d{2,4}-\d{2,4}-)\d{4}", RegexOptions.Compiled);
public static string MaskPhone(string? text)
{
if (string.IsNullOrEmpty(text))
{
return text ?? string.Empty;
}
return PhoneRegex.Replace(text, "$1****");
}
}
C#呼び出し側はこう書けます。
string masked = MaskUtil.MaskPhone("電話番号: 090-1234-5678");
// 電話番号: 090-1234-****
C#「どんな正規表現でどう置換しているか」はユーティリティの中に閉じ込めて、
呼び出し側は「何をしたいか」だけを書く——
これが、実務で長く保守されるコードの書き方です。
まとめ 「正規表現置換ユーティリティ」は“一括修正と整形のための仕上げ道具”
正規表現置換は、「パターンで探して、一気に書き換える」ための強力な道具です。
押さえておきたいポイントを整理すると、
Regex.Replace は「パターンにマッチした部分」をまとめて置換する。
キャプチャグループ+$1/${name} を使うと、並べ替えやフォーマット変換が簡単に書ける。MatchEvaluator を使うと、「マッチした内容に応じて動的に置換結果を決める」ことができる。
null 安全な ReplaceSafe のようなユーティリティを用意しておくと、呼び出し側がすっきりする。
パターンと置換ロジックは、「意味のある名前のメソッド」に閉じ込めて、呼び出し側から正規表現を隠すと保守性が上がる。
ここまで理解できれば、「とりあえず Regex.Replace を書いている」段階から一歩進んで、
“業務ロジックに沿った一括修正・整形の仕上げ道具”として、
正規表現置換ユーティリティを設計・実装できるようになっていきます。
