C# Tips | 文字列処理:正規表現置換

C# C#
スポンサーリンク

はじめに 「正規表現置換」は“パターンで探して、一気に書き換える”技

一致判定は「合っているかどうか」、抽出は「取り出す」でした。
正規表現置換は、そのさらに一歩先——「合っているところを、別の形に書き換える」ための道具です。

電話番号の一部をマスクする。
日付のフォーマットを 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-dddd/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 を書いている」段階から一歩進んで、
“業務ロジックに沿った一括修正・整形の仕上げ道具”として、
正規表現置換ユーティリティを設計・実装できるようになっていきます。

タイトルとURLをコピーしました