C# Regexの使い方を完全解説|正規表現の判定・抽出・置換をサンプルコードで理解する

11. C#の正規表現でよく使う実践パターン

11-1. 数字のみを許可するパターン

半角数字が1文字以上入力されていることを判定します。

string pattern = @"\A[0-9]+\z";

空文字を許可する場合は*を使用します。

string pattern = @"\A[0-9]*\z";

\dはUnicodeの数字にも一致するため、半角数字だけを許可する場合は[0-9]が適しています。

11-2. 半角英数字のみを許可するパターン

半角の英大文字、英小文字、数字だけを許可します。

string pattern = @"\A[A-Za-z0-9]+\z";

大文字と小文字を区別せずに書く場合は、IgnoreCaseも利用できます。

bool result = Regex.IsMatch(input,@"\A[a-z0-9]+\z",RegexOptions.IgnoreCase);

アンダースコアも許可する場合は次のようにします。

\A[A-Za-z0-9_]+\z

11-3. 指定した文字数を判定するパターン

任意の文字がちょうど8文字あるかを判定します。

string pattern = @"\A.{8}\z";

ただし、.は通常、改行に一致しません。

半角英数字が8文字の場合は、文字クラスを明示します。

string pattern = @"\A[A-Za-z0-9]{8}\z";

8文字以上16文字以下の場合は次のとおりです。

string pattern = @"\A[A-Za-z0-9]{8,16}\z";

11-4. 郵便番号を判定するパターン

ハイフンありの郵便番号です。

\A[0-9]{3}-[0-9]{4}\z

ハイフンを任意にする場合は、?を付けます。

\A[0-9]{3}-?[0-9]{4}\z
Console.WriteLine(Regex.IsMatch("123-4567", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("1234567", pattern));  // True

11-5. 電話番号を判定するパターン

一般的なハイフン区切り形式の簡易パターンです。

\A0[0-9]{1,4}-[0-9]{1,4}-[0-9]{4}\z

携帯電話番号の簡易パターンです。

\A0[789]0-?[0-9]{4}-?[0-9]{4}\z

電話番号は種類によって桁構成が異なるため、対象とする番号体系を明確にしてパターンを作成してください。

11-6. メールアドレスを判定するパターン

入力チェック向けの簡易パターンです。

\A[^@\s]+@[^@\s]+\.[^@\s]+\z
bool result = Regex.IsMatch("user@example.com",@"\A[^@\s]+@[^@\s]+\.[^@\s]+\z");

このパターンは、明らかな入力ミスを検出する目的に向いています。メールアドレスが実際に使用できるかは、確認メールなどで検証してください。

11-7. URLを抽出するパターン

HTTPまたはHTTPSのURLを抽出する簡易パターンです。

https?://[^\s<>"']+
string input = "詳細はhttps://example.com/docsを確認してください。";Match match = Regex.Match(input, @"https?://[^\s<>'""]+");

厳密なURL解析や正規化には、抽出後にUri.TryCreateを使用する方法があります。

if (match.Success&& Uri.TryCreate(match.Value, UriKind.Absolute, out Uri? uri)){Console.WriteLine(uri.Host);}

11-8. 日付を判定・抽出するパターン

yyyy-MM-dd形式の簡易パターンです。

\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z

年、月、日を抽出する場合は、名前付きグループを使用します。

(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})

形式だけでなく日付の有効性も確認する例です。

using System.Globalization;

string input = "2026-06-20";

bool valid = DateTime.TryParseExact(input,"yyyy-MM-dd",CultureInfo.InvariantCulture,DateTimeStyles.None,out DateTime date);

11-9. HTMLタグを削除するパターン

単純なHTML風文字列からタグを取り除く例です。

string html = "<p>Hello <strong>C#</strong></p>";string text = Regex.Replace(html, @"<[^>]*>", "");

Console.WriteLine(text); // Hello C#

ただし、HTMLは正規表現だけで安全かつ完全に解析できるほど単純ではありません。属性内に>が含まれる場合や、script、style、コメント、壊れたHTMLなどでは想定どおりに処理できない可能性があります。

実際のHTMLを解析・変換する用途では、HTMLパーサーの使用を検討してください。

11-10. ファイル名と拡張子を抽出するパターン

最後のピリオドを基準に、ファイル名と拡張子を抽出する簡易例です。

string input = "report.final.pdf";string pattern = @"\A(?<name>.+).(?<extension>[^.]+)\z";

Match match = Regex.Match(input, pattern);

if (match.Success){Console.WriteLine(match.Groups["name"].Value);// report.final

Console.WriteLine(match.Groups<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["extension"]</span>.Value);// pdf

}

実際のファイルパスを扱う場合は、正規表現よりPath.GetFileNameWithoutExtensionPath.GetExtensionが適しています。

using System.IO;

string name = Path.GetFileNameWithoutExtension(input);string extension = Path.GetExtension(input);

12. 貪欲マッチと最短マッチの違い

12-1. 貪欲マッチで想定より長く抽出される理由

+などの量指定子は、標準では可能な限り長い文字列に一致します。これを貪欲マッチと呼びます。

string input = "<b>one</b><b>two</b>";Match match = Regex.Match(input, @"<b>.</b>");

Console.WriteLine(match.Value);

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

<b>one</b><b>two</b>

.が可能な限り長く一致するため、最初の<b>から最後の</b>までが取得されます。

12-2. 「.」と「.?」の違い

.は改行以外の任意の1文字に一致します。

.

.?は「任意の1文字が0回または1回」です。

.?

貪欲マッチと最短マッチの比較でよく使用されるのは、..?ではなく、..?です。

.   → 任意の文字を0文字以上、できるだけ長く取得.?  → 任意の文字を0文字以上、できるだけ短く取得
Match greedy = Regex.Match(input, @"<b>.</b>");Match lazy = Regex.Match(input, @"<b>.?</b>");

lazy.Valueは次の値になります。

<b>one</b>

12-3. 記号で囲まれた文字列を最短で抽出する

角括弧で囲まれた部分を最短で抽出する例です。

string input = "[apple][banana][orange]";string pattern = @"<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">[.?]";

foreach (Match match in Regex.Matches(input, pattern)){Console.WriteLine(match.Value);}

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

[apple][banana][orange]

12-4. 否定文字クラスを使って安全に抽出する

区切り文字が明確な場合は、.と最短マッチを組み合わせるより、否定文字クラスを使用したほうが意図を表現しやすくなります。

[[^]]]

簡略化すると、文字クラス内では次のようにも記述できます。

[[^]]]
string input = "[apple][banana]";MatchCollection matches = Regex.Matches(input, @"[[^]]]");

このパターンは、閉じ角括弧]以外の文字を0文字以上取得します。終端が明確になるため、.?より効率的に動作する場合があります。

13. 先読み・後読みを使った高度な検索

13-1. 肯定先読み「(?= )」の使い方

肯定先読み(?=...)は、後ろに指定した条件が続く位置に一致します。条件部分は抽出結果に含まれません。

「円」の直前にある数字を抽出する例です。

string input = "価格は1200円、送料は500円です。";string pattern = @"[0-9]+(?=円)";

foreach (Match match in Regex.Matches(input, pattern)){Console.WriteLine(match.Value);}

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

1200500

13-2. 否定先読み「(?! )」の使い方

否定先読み(?!...)は、後ろに指定した条件が続かない位置に一致します。

tempから始まらない英単語を抽出する例です。

string input = "test temp temporary sample";string pattern = @"\b(?!temp\b)[A-Za-z]+\b";

foreach (Match match in Regex.Matches(input, pattern)){Console.WriteLine(match.Value);}

特定の拡張子を除外する例です。

\A.+.(?!exe\z)[A-Za-z0-9]+\z

13-3. 肯定後読み「(?<= )」の使い方

肯定後読み(?<=...)は、直前に指定した条件がある位置に一致します。条件部分は結果に含まれません。

商品番号:の後ろにある数字を抽出する例です。

string input = "商品番号:12345";string pattern = @"(?<=商品番号:)[0-9]+";

Match match = Regex.Match(input, pattern);

Console.WriteLine(match.Value); // 12345

13-4. 否定後読み「(?<! )」の使い方

否定後読み(?<!...)は、直前に指定した条件がない位置に一致します。

ドル記号が付いていない数字を検索する簡易例です。

string input = "$100 200 $300 400";string pattern = @"(?<!$)\b[0-9]+\b";

foreach (Match match in Regex.Matches(input, pattern)){Console.WriteLine(match.Value);}

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

200400

13-5. 条件部分を結果に含めず抽出するサンプル

先読みや後読みを使うと、前後の目印を結果に含めずに値だけを取得できます。

string input = "ID=[ABC-123]";string pattern = @"(?<=ID=[)[A-Z]+-[0-9]+(?=])";

Match match = Regex.Match(input, pattern);

Console.WriteLine(match.Value); // ABC-123

キャプチャグループを使って同じ処理を書くこともできます。

Match match = Regex.Match(input, @"ID=[(?<id>[A-Z]+-[0-9]+)]");

Console.WriteLine(match.Groups["id"].Value);

前後の条件が複雑な場合は、名前付きグループのほうが読みやすくなることもあります。

14. Regexでエラーや意図しない結果が出る場合の対処法

14-1. バックスラッシュのエスケープ漏れを確認する

C#文字列と正規表現の両方でバックスラッシュが特別な意味を持ちます。

次の通常文字列はコンパイルエラーになります。

// string pattern = "\d+";

通常文字列では、バックスラッシュを二重にします。

string pattern = "\d+";

逐語的文字列リテラルを使用すると、より読みやすくなります。

string pattern = @"\d+";

14-2. Regex.Escapeで入力文字列を安全に処理する

ユーザー入力や変数の値を正規表現パターンに組み込む場合は、Regex.Escapeを使用します。

string keyword = "C++";string pattern = Regex.Escape(keyword);

bool result = Regex.IsMatch("C++を学ぶ", pattern);

Console.WriteLine(result); // True

エスケープしない場合、+が量指定子として解釈され、意図しないパターンになります。

前後にパターンを追加する場合も同様です。

string keyword = Console.ReadLine() ?? "";string pattern = $@"\b{Regex.Escape(keyword)}\b";

ただし、キーワードが空文字の場合の挙動は別途確認してください。

14-3. 「^」と「$」を付け忘れていないか確認する

入力全体を検証するつもりでも、アンカーがないと部分一致になります。

bool result = Regex.IsMatch("abc123", @"[0-9]+");

// True

数字だけを許可する場合は、次のようにします。

bool result = Regex.IsMatch("abc123", @"\A[0-9]+\z");

// False

14-4. 全角文字と半角文字の違いを確認する

次のパターンは半角数字だけに一致します。

[0-9]+

全角数字も対象にしたい場合は、要件に応じて\dを使用できます。

\d+

ただし、\dは全角数字以外のUnicode数字にも一致する可能性があります。

半角と全角の数字だけを明示する場合は、次のような文字クラスも使えます。

[0-90-9]+

入力を統一したい場合は、正規表現による判定の前に文字を正規化する方法もあります。

using System.Text;

string normalized = input.Normalize(NormalizationForm.FormKC);

14-5. 改行に一致しない場合の対処法

.は標準では改行に一致しません。

string input = "BEGIN\nCONTENT\nEND";bool result = Regex.IsMatch(input, @"BEGIN.*END");

// False

RegexOptions.Singlelineを指定します。

bool result = Regex.IsMatch(input,@"BEGIN.END",RegexOptions.Singleline);

または、改行を含む任意の文字を表すパターンを使用します。

[\s\S]

14-6. グループ番号がずれる場合の対処法

パターンに丸括弧を追加すると、後続のグループ番号が変わります。

([A-Z]+)-([0-9]+)

途中に別のグループを追加すると、Groups[2]の意味が変わる可能性があります。

対策として、抽出不要なグループには非キャプチャグループを使用します。

(?:ABC|DEF)-([0-9]+)

重要な値には名前付きグループを使用します。

(?:ABC|DEF)-(?<number>[0-9]+)

14-7. nullや空文字を処理する際の注意点

Regexメソッドにnullを渡すと、例外が発生します。事前に確認してください。

string? input = GetInput();

if (!string.IsNullOrEmpty(input)){bool result = Regex.IsMatch(input, @"[0-9]+");}

空文字に一致するパターンもあります。

Console.WriteLine(Regex.IsMatch("", @"\A[0-9]\z")); // TrueConsole.WriteLine(Regex.IsMatch("", @"\A[0-9]+\z")); // False

は0回以上、+は1回以上である点に注意してください。

15. Regexを安全かつ高速に使うためのポイント

15-1. Regexのタイムアウトを指定する

外部から受け取った文字列を正規表現で処理する場合は、タイムアウトを指定します。

TimeSpan timeout = TimeSpan.FromSeconds(1);

try{bool result = Regex.IsMatch(input,pattern,RegexOptions.None,timeout);}catch (RegexMatchTimeoutException){Console.WriteLine("正規表現の処理がタイムアウトしました。");}

Regexインスタンスを作成する場合も指定できます。

var regex = new Regex(pattern,RegexOptions.None,TimeSpan.FromSeconds(1));

15-2. バックトラッキングによる処理遅延を避ける

正規表現エンジンは、一致に失敗すると別の組み合わせを試すことがあります。これをバックトラッキングと呼びます。

曖昧な量指定子を重ねると、入力によっては試行回数が急増します。

(a+)+$

改善策として、次の方法があります。

  • 曖昧な.*を必要以上に使わない

  • 終端が明確なら否定文字クラスを使う

  • 入力文字数を制限する

  • タイムアウトを設定する

  • 不要な選択肢や入れ子の量指定子を減らす

  • 利用可能な環境ではRegexOptions.NonBacktrackingを検討する

var regex = new Regex(@"\A[A-Za-z0-9]+\z",RegexOptions.NonBacktracking,TimeSpan.FromSeconds(1));

NonBacktrackingでは使用できない正規表現機能もあるため、既存パターンへ適用する際は動作確認が必要です。

15-3. ReDoSを引き起こしやすいパターンに注意する

ReDoSは、正規表現の過剰なバックトラッキングを利用してCPU資源を消費させる攻撃です。

特に注意が必要なのは、次のような条件です。

  • ユーザーが非常に長い入力を送信できる

  • 量指定子が入れ子になっている

  • 複数の選択肢が同じ文字列に一致し得る

  • タイムアウトが設定されていない

  • ユーザーが正規表現パターン自体を指定できる

正規表現パターンをユーザーに自由入力させる設計は避け、必要であれば利用できる構文や入力長を制限してください。

15-4. 同じパターンを繰り返し使う場合はRegexを再利用する

ループ内で同じRegexオブジェクトを毎回作成するのは避けます。

// 毎回作成する例foreach (string value in values){var regex = new Regex(@"\A[0-9]+\z");Console.WriteLine(regex.IsMatch(value));}

ループの外で作成して再利用します。

var regex = new Regex(@"\A[0-9]+\z",RegexOptions.None,TimeSpan.FromSeconds(1));

foreach (string value in values){Console.WriteLine(regex.IsMatch(value));}

複数のスレッドから利用する設計では、共有する状態や周辺コードも含めて安全性を確認してください。

15-5. GeneratedRegexを使って正規表現を生成する

.NET 7以降では、GeneratedRegex属性を使用して、コンパイル時に正規表現の実装を生成できます。

using System.Text.RegularExpressions;

public static partial class AppRegex{[GeneratedRegex(@"\A[0-9]+\z",RegexOptions.CultureInvariant,matchTimeoutMilliseconds: 1000)]public static partial Regex DigitsOnly();}

使用方法は次のとおりです。

bool result = AppRegex.DigitsOnly().IsMatch("12345");

Console.WriteLine(result); // True

ソース生成を使用すると、パターンをコード上で明確に管理でき、起動時の解析コストや実行性能の改善につながる場合があります。

GeneratedRegexを定義する型とメソッドには、partialが必要です。対象フレームワークによって利用可能な宣言形式が異なるため、プロジェクトの.NETバージョンを確認してください。

15-6. 正規表現を使わない文字列処理との使い分け

Regexは便利ですが、すべての文字列処理に適しているわけではありません。

固定文字列が含まれるかだけを確認する場合は、Containsが明確です。

bool result = input.Contains("ERROR");

先頭や末尾を確認する場合は、StartsWithEndsWithを使えます。

bool starts = input.StartsWith("ID:");bool ends = input.EndsWith(".txt");

固定文字で分割する場合は、string.Splitを使います。

string[] values = input.Split(',');

ファイルパスにはPathクラス、URLにはUriクラス、日付にはDateTimeDateOnlyを優先します。

正規表現は、複数の文字条件を組み合わせた検索・抽出・置換が必要な場合に使用すると効果的です。

16. C#のRegexに関するよくある質問

16-1. Regex.MatchとRegex.Matchesはどう使い分ける?

最初の1件だけ必要な場合はRegex.Matchを使用します。

Match match = Regex.Match(input, pattern);

一致したすべての結果が必要な場合はRegex.Matchesを使用します。

MatchCollection matches = Regex.Matches(input, pattern);

MatchからNextMatchを繰り返すこともできますが、全件処理ではMatchesforeachの組み合わせが分かりやすいでしょう。

16-2. Regex.IsMatchとstring.Containsの違いは?

string.Containsは、固定された文字列が含まれるかを確認します。

bool result = input.Contains("123");

Regex.IsMatchは、文字の種類や繰り返し回数を含むパターンに一致するか確認します。

bool result = Regex.IsMatch(input, @"[0-9]{3}");

固定文字列を探すだけならContains、形式や規則を判定する場合はRegex.IsMatchが適しています。

16-3. 正規表現で完全一致を判定するには?

入力全体をアンカーで囲みます。

bool result = Regex.IsMatch(input,@"\A[A-Za-z0-9]+\z");

一般的には^$も使用されます。

bool result = Regex.IsMatch(input,@"^[A-Za-z0-9]+$");

改行を含む可能性がある入力全体を厳密に扱う場合は、\A\zが分かりやすい指定です。

16-4. 一致した複数の値を配列やListに格納するには?

LINQを使用してリストに変換できます。

using System.Linq;

List<string> values = Regex.Matches(input, @"[0-9]+").Cast<Match>().Select(match => match.Value).ToList();

配列にする場合は、ToArrayを使用します。

string[] values = Regex.Matches(input, @"[0-9]+").Cast<Match>().Select(match => match.Value).ToArray();

16-5. 正規表現パターンを変数で組み立てるには?

文字列補間を使用できます。

int minLength = 4;int maxLength = 8;

string pattern = $@"\A[A-Za-z0-9]{{{minLength},{maxLength}}}\z";

文字列補間では波括弧が変数展開に使われるため、正規表現の量指定子として出力する波括弧は二重にします。

生成されるパターンは次のとおりです。

\A[A-Za-z0-9]{4,8}\z

16-6. ユーザー入力を正規表現に含めても安全?

ユーザー入力をそのままパターンに結合すると、入力に含まれる.*+(などが正規表現として解釈されます。

string keyword = userInput;string pattern = keyword; // 非推奨

リテラル文字列として検索する場合は、Regex.Escapeを使用します。

string keyword = userInput;string pattern = Regex.Escape(keyword);

bool result = Regex.IsMatch(input, pattern);

ユーザーに正規表現パターン自体を入力させる場合は、Regex.Escapeでは目的を満たせません。その場合は、入力長、利用可能な構文、実行時間を制限し、必ずタイムアウトを設定してください。

16-7. 正規表現をテスト・デバッグする方法は?

正規表現を確認するときは、成功例だけでなく失敗例も用意します。

string pattern = @"\A[0-9]{3}-[0-9]{4}\z";

string[] validValues ={"123-4567","000-0000"};

string[] invalidValues ={"12-34567","1234567","abc-defg","123-4567-extra",""};

単体テストでは、期待値を明確にします。

[Theory][InlineData("123-4567", true)][InlineData("1234567", false)][InlineData("abc-defg", false)][InlineData("", false)]public void PostalCodePattern_ReturnsExpectedResult(string input,bool expected){bool actual = Regex.IsMatch(input,@"\A[0-9]{3}-[0-9]{4}\z");

Assert.Equal(expected, actual);

}

デバッグ時は、Match.Valueだけでなく、次のプロパティも確認します。

Console.WriteLine(match.Success);Console.WriteLine(match.Value);Console.WriteLine(match.Index);Console.WriteLine(match.Length);

グループを使用している場合は、すべてのグループを出力すると原因を見つけやすくなります。

for (int i = 0; i < match.Groups.Count; i++){Group group = match.Groups[i];

Console.WriteLine($"Groups<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">[{i}]</span>: Success={group.Success}, Value={group.Value}");

}

テストケースには、長い文字列、空文字、改行、全角文字、末尾の空白、境界値なども含めてください。

まとめ

C#で正規表現を使用するには、System.Text.RegularExpressions名前空間のRegexクラスを利用します。

一致判定にはRegex.IsMatch、最初の一致の抽出にはRegex.Match、すべての一致の抽出にはRegex.Matchesを使用します。一致部分の置換や削除にはRegex.Replace、パターンに基づく分割にはRegex.Splitが利用できます。

入力全体を判定するときは、^$、または\A\zを付け忘れないことが重要です。また、半角数字だけを対象にする場合は、Unicode数字にも一致する\dではなく[0-9]を使用します。

Regexを安全に利用するためには、ユーザー入力をパターンへ組み込む際にRegex.Escapeを使用し、信頼できない入力を処理する場合はタイムアウトを設定してください。曖昧な量指定子や量指定子の入れ子は、バックトラッキングによる処理遅延やReDoSにつながる可能性があります。

固定文字列の検索や単純な分割では、ContainsStartsWithEndsWithstring.Splitなどのほうが適しています。正規表現と通常の文字列処理を使い分けることで、読みやすく安全なC#コードを実装できます。