C#正規表現の基本から使い方まで完全解説|初心者がつまずくパターン・置換・判定を実例で理解
はじめに
C#で文字列を扱っていると、「数字だけか判定したい」「文章からメールアドレスを抽出したい」「複数の空白を1つにまとめたい」といった処理が頻繁に登場します。このような文字列の検索・判定・抽出・置換を、パターンとしてまとめて記述できる仕組みが正規表現です。
正規表現は短い記述で複雑な条件を表現できる一方、メタ文字やバックスラッシュの扱いを理解していないと、意図しない文字列に一致したり、まったく一致しなかったりします。特にC#では、正規表現のエスケープと文字列リテラルのエスケープが重なる点に注意が必要です。
この記事では、C#のRegexクラスを使った基本的な判定・検索・置換から、グループ、先読み、タイムアウト、GeneratedRegexまでを実例付きで解説します。
コード例では、基本的に次の名前空間を使用します。
using System;using System.Collections.Generic;using System.Linq;using System.Text.RegularExpressions;1. C#の正規表現とは?初心者向けに基本を解説
1-1. 正規表現でできる文字列検索・判定・置換
正規表現とは、文字列の特徴をパターンとして表現するための記法です。
例えば、次のパターンは「3桁の数字、ハイフン、4桁の数字」という形式を表します。
\d{3}-\d{4}このパターンを利用すると、次のような処理を実装できます。
文字列が郵便番号の形式か判定する
文章から郵便番号らしい部分を検索する
郵便番号のハイフンを削除する
郵便番号を前半3桁と後半4桁に分けて抽出する
正規表現では、同じパターンを判定・検索・抽出・置換に利用できます。
1-2. C#で正規表現を扱うRegexクラス
C#では、.NETのSystem.Text.RegularExpressions.Regexクラスを使用して正規表現を処理します。Regexクラスには、文字列の判定、検索、複数検索、置換、分割を行うメソッドが用意されています。
代表的なメソッドは次のとおりです。
| メソッド | 用途 |
|---|---|
Regex.IsMatch | 一致する部分が存在するか判定する |
Regex.Match | 最初に一致した結果を取得する |
Regex.Matches | 一致したすべての結果を取得する |
Regex.Replace | 一致部分を置換する |
Regex.Split | 一致部分を区切りとして分割する |
例えば、文字列が数字だけで構成されているかは次のように判定できます。
bool result = Regex.IsMatch("12345", @"\A\d+\z");Console.WriteLine(result); // True
1-3. 通常の文字列検索と正規表現の違い
通常の文字列検索は、検索対象となる文字が決まっている場合に適しています。
string text = "C#の正規表現を学ぶ";bool contains = text.Contains("正規表現");int index = text.IndexOf("正規表現", StringComparison.Ordinal);
一方、正規表現は検索対象をパターンで指定したい場合に向いています。
string text = "商品番号はA-1234です。";bool containsProductCode =Regex.IsMatch(text, @"[A-Z]-\d{4}");
固定文字列を探すだけなら、Contains、StartsWith、EndsWith、IndexOfなどの文字列メソッドのほうが簡潔で高速です。
次のように条件が変化する場合は、正規表現が役立ちます。
数字の桁数を指定する
複数の候補から検索する
前後の文字を条件にする
一致部分をグループに分けて抽出する
表記の揺れを許容する
1-4. 正規表現が役立つ代表的な利用シーン
C#の正規表現は、次のような処理で利用できます。
郵便番号や電話番号の形式判定
ログから日時やエラーレベルを抽出
連続する空白や改行の整形
商品コードや受付番号の検索
ファイル名や識別子の形式判定
個人情報のマスキング
テキストからURLやメールアドレスを抽出
特定のキーワードを含む行の検索
ただし、正規表現だけでデータの意味まで検証できるとは限りません。例えば、日付の形式が2026/02/31でも、単純な正規表現では形式上正しいと判定される可能性があります。
形式の判定と、実際に有効な値かどうかの判定は分けて考えることが重要です。
2. C#で正規表現を使うための基本構文
2-1. Regex.IsMatchで文字列がパターンに一致するか判定する
Regex.IsMatchは、入力文字列の中に正規表現パターンと一致する部分が存在するかをboolで返します。
string input = "注文番号は12345です。";bool result = Regex.IsMatch(input, @"\d+");
Console.WriteLine(result); // True
この例では、文字列中に1文字以上の数字があるためtrueになります。
注意点は、IsMatchがデフォルトでは部分一致を行うことです。
bool result = Regex.IsMatch("abc123xyz", @"\d+");Console.WriteLine(result); // True
文字列全体が数字であることを確認したい場合は、先頭と末尾を指定します。
bool result = Regex.IsMatch("12345", @"\A\d+\z");Console.WriteLine(result); // True
2-2. Regex.Matchで最初に一致した文字列を取得する
Regex.Matchは、最初に一致した結果をMatchオブジェクトとして返します。
string input = "商品Aは1200円、商品Bは980円です。";Match match = Regex.Match(input, @"\d+");
if (match.Success){Console.WriteLine(match.Value); // 1200Console.WriteLine(match.Index); // 一致開始位置Console.WriteLine(match.Length); // 4}
一致しなかった場合でもnullが返るわけではありません。SuccessがfalseのMatchオブジェクトが返ります。
Match match = Regex.Match("abc", @"\d+");Console.WriteLine(match.Success); // FalseConsole.WriteLine(match.Value); // 空文字
2-3. Regex.Matchesで一致した文字列をすべて取得する
Regex.Matchesは、一致したすべての結果をMatchCollectionとして返します。
string input = "商品Aは1200円、商品Bは980円です。";MatchCollection matches = Regex.Matches(input, @"\d+");
foreach (Match match in matches){Console.WriteLine(match.Value);}
実行結果は次のとおりです。
1200980LINQで処理したい場合は、Cast<Match>()を利用できます。
List<int> prices = Regex.Matches(input, @"\d+").Cast<Match>().Select(match => int.Parse(match.Value)).ToList();2-4. Regex.Replaceで一致部分を置換する
Regex.Replaceは、パターンに一致した部分を別の文字列に置換します。
string input = "電話番号は090-1234-5678です。";string result = Regex.Replace(input, @"\d", "*");
Console.WriteLine(result);// 電話番号は--***です。
置換文字列に空文字を指定すると、一致部分を削除できます。
string input = "A1B2C3";string result = Regex.Replace(input, @"\d", "");
Console.WriteLine(result); // ABC
2-5. Regex.Splitで文字列を分割する
Regex.Splitは、正規表現に一致した部分を区切りとして文字列を分割します。
string input = "apple, banana;orange grape";string[] values = Regex.Split(input, @"[,;\s]+");
foreach (string value in values){Console.WriteLine(value);}
実行結果は次のとおりです。
applebananaorangegrape単一の固定文字で分割するだけなら、通常はstring.Splitのほうが分かりやすいでしょう。
2-6. staticメソッドとRegexインスタンスの使い分け
Regexは、staticメソッドとインスタンスメソッドの両方で使用できます。
一度だけ使用する簡単なパターンでは、staticメソッドが手軽です。
bool result = Regex.IsMatch("12345", @"\A\d+\z");同じパターンを繰り返し使用する場合は、Regexインスタンスとして保持すると、パターンやオプションを一か所で管理できます。
Regex numberRegex = new(@"\A\d+\z",RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));bool result1 = numberRegex.IsMatch("12345");bool result2 = numberRegex.IsMatch("12A45");
アプリケーション全体で繰り返し使用する場合は、static readonlyフィールドやGeneratedRegexも選択肢になります。
3. C#正規表現でよく使うパターンとメタ文字
3-1. 任意の1文字を表す「.」
.は、原則として改行以外の任意の1文字に一致します。
string pattern = @"c.t";Console.WriteLine(Regex.IsMatch("cat", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("cut", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("ct", pattern)); // False
.そのものを検索したい場合は、.と記述します。
bool result = Regex.IsMatch("file.txt", @"file.txt");RegexOptions.Singlelineを指定すると、.が改行にも一致します。
3-2. 数字・英数字・空白を表す「\d」「\w」「\s」
よく使用する文字クラスには、次のものがあります。
| パターン | 意味 |
\d | 数字 |
\D | 数字以外 |
\w | 単語構成文字 |
\W | 単語構成文字以外 |
\s | 空白文字 |
\S | 空白文字以外 |
Console.WriteLine(Regex.IsMatch("123", @"\d+")); // TrueConsole.WriteLine(Regex.IsMatch("abc", @"\w+")); // TrueConsole.WriteLine(Regex.IsMatch("a b", @"\s")); // True.NETの\dは、初期設定ではASCIIの0から9だけを意味するわけではありません。Unicodeの10進数字にも一致します。
半角数字だけに限定したい場合は、次のように明示します。
string pattern = @"\A[0-9]+\z";同様に、\wは英数字とアンダースコアだけでなく、日本語などのUnicode文字にも一致する可能性があります。半角英数字とアンダースコアだけに限定したい場合は、[A-Za-z0-9_]を使用します。
3-3. 文字の範囲を指定する文字クラス「[]」
[]は、指定した文字のいずれか1文字に一致します。
string pattern = @"[abc]";Console.WriteLine(Regex.IsMatch("apple", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("dog", pattern)); // False
ハイフンを使うと範囲を指定できます。
[0-9][A-Z][a-z][A-Za-z]例えば、半角英字だけで構成されているかは次のように判定できます。
bool result = Regex.IsMatch("Hello", @"\A[A-Za-z]+\z");文字クラス内の-を通常のハイフンとして扱う場合は、先頭または末尾に置くか、エスケープします。
[-0-9][0-9-][0-9-]3-4. 指定した文字以外に一致させる否定文字クラス「[^]」
文字クラスの先頭に^を置くと、指定した文字以外の1文字に一致します。
string input = "abc123";MatchCollection matches = Regex.Matches(input, @"[^0-9]");
foreach (Match match in matches){Console.WriteLine(match.Value);}
この例では、a、b、cが一致します。
数字以外を削除して数字だけを残す処理にも利用できます。
string input = "TEL: 090-1234-5678";string digits = Regex.Replace(input, @"[^0-9]", "");
Console.WriteLine(digits); // 09012345678
3-5. 行頭と行末を表す「^」「$」
^は先頭、$は末尾を表します。
string pattern = @"^\d+$";Console.WriteLine(Regex.IsMatch("12345", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("A123", pattern)); // False
RegexOptions.Multilineを指定すると、^と$は文字列全体だけでなく各行の先頭と末尾にも一致します。
string input = """INFO StartERROR FailedINFO End""";MatchCollection matches = Regex.Matches(input,@"^ERROR.*$",RegexOptions.Multiline);
なお、$は文字列末尾の改行直前に一致する場合があります。文字列全体を厳密に判定したい場合は、\Aと\zを使用すると意図が明確です。
3-6. 文字列の先頭と末尾を表す「\A」「\z」
\Aは文字列全体の先頭、\zは文字列全体の末尾に一致します。
string pattern = @"\A[0-9]+\z";Console.WriteLine(Regex.IsMatch("12345", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("123\n", pattern)); // False
RegexOptions.Multilineを指定しても、\Aと\zの意味は変わりません。
入力値の完全一致判定では、次の形を基本にすると安全です。
\A(?:判定したいパターン)\z3-7. いずれかのパターンに一致させる「|」
|は、左右どちらかのパターンに一致させる選択を表します。
string pattern = @"cat|dog";Console.WriteLine(Regex.IsMatch("I have a dog.", pattern)); // True
複数文字の候補をまとめたい場合は、グループと組み合わせます。
string pattern = @"\A(?:red|green|blue)\z";候補に共通する接頭辞がある場合は、共通部分を外に出すと読みやすくなります。
(?:https?|ftp)これはhttp、https、ftpのいずれかに一致します。
3-8. パターンをグループ化する「()」
丸括弧()は、複数の要素をひとまとまりとして扱うために使用します。
string pattern = @"(ab)+";Console.WriteLine(Regex.IsMatch("abab", pattern)); // True
通常の丸括弧は、一致した内容をキャプチャします。
Match match = Regex.Match("2026-06-20", @"(\d{4})-(\d{2})-(\d{2})");Console.WriteLine(match.Groups[1].Value); // 2026Console.WriteLine(match.Groups[2].Value); // 06Console.WriteLine(match.Groups[3].Value); // 20
グループ化だけが目的で、結果を保存する必要がない場合は、非キャプチャグループ(?:...)を使用します。
(?:jpg|png|gif)3-9. 特殊文字を通常の文字として検索するエスケープ
正規表現で特別な意味を持つ文字を、その文字自体として検索する場合は、バックスラッシュでエスケープします。
代表的なメタ文字は次のとおりです。
. ^ $ * + ? ( ) [ ] { } \ |例えば、金額に含まれる+を検索する場合は+と記述します。
bool result = Regex.IsMatch("A+B", @"A+B");ピリオドは任意の1文字を表すため、拡張子を検索するときはエスケープが必要です。
bool result = Regex.IsMatch("photo.jpg", @".jpg\z");外部入力をそのまま検索パターンとして使用する場合は、手動でエスケープせずRegex.Escapeを使用します。
4. 繰り返し回数を指定する量指定子の使い方
4-1. 0回以上の繰り返しを表す「」
は、直前の要素が0回以上繰り返されることを表します。
string pattern = @"ab*c";Console.WriteLine(Regex.IsMatch("ac", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("abc", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("abbbc", pattern)); // True
bが0文字でも一致する点が重要です。
.* は「改行以外の任意の文字が0文字以上」という意味になります。広範囲に一致しやすいため、無条件に使用すると想定以上の文字を取得することがあります。
4-2. 1回以上の繰り返しを表す「+」
+は、直前の要素が1回以上繰り返されることを表します。
string pattern = @"ab+c";Console.WriteLine(Regex.IsMatch("ac", pattern)); // FalseConsole.WriteLine(Regex.IsMatch("abc", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("abbbc", pattern)); // True
数字が1文字以上という条件は、次のように記述できます。
[0-9]+*と異なり、最低1回の一致が必要です。
4-3. 0回または1回を表す「?」
?は、直前の要素が0回または1回出現することを表します。
string pattern = @"colou?r";Console.WriteLine(Regex.IsMatch("color", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("colour", pattern)); // True
電話番号のハイフンを省略可能にする場合は、次のように記述できます。
\A[0-9]{3}-?[0-9]{4}\zただし、区切り文字を複数箇所で省略可能にすると、不統一な形式まで許可する可能性があります。
4-4. 回数を指定する「{n}」「{n,}」「{n,m}」
波括弧を使うと、繰り返し回数を細かく指定できます。
| パターン | 意味 |
{n} | ちょうどn回 |
{n,} | n回以上 |
{n,m} | n回以上m回以下 |
郵便番号は次のように表現できます。
\A[0-9]{3}-[0-9]{4}\z4文字以上の英字は次のように表現できます。
\A[A-Za-z]{4,}\z8文字以上16文字以下は次のように表現できます。
\A.{8,16}\z文字数の判定では、改行やUnicodeの結合文字、サロゲートペアなども考慮が必要です。見た目上の文字数を厳密に数える用途では、正規表現だけでなくUnicodeのテキスト要素も検討します。
4-5. 最長一致と最短一致の違い
通常の量指定子は貪欲で、条件を満たす範囲のうち、できるだけ長い部分に一致しようとします。
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>まで取得されます。
最短の範囲を取得したい場合は、非貪欲な量指定子を使用します。
MatchCollection matches = Regex.Matches(input, @"<b>.?</b>");foreach (Match item in matches){Console.WriteLine(item.Value);}
実行結果は次のとおりです。
<b>one</b><b>two</b>4-6. 貪欲な量指定子を非貪欲にする方法
量指定子の後ろに?を付けると、非貪欲になります。
| 貪欲 | 非貪欲 |
| ? |
+ | +? |
? | ?? |
{n,m} | {n,m}? |
例えば、ダブルクォーテーションで囲まれた最短の文字列を取得する場合は、次のように記述できます。
string input = """name="Taro" age="20" """;MatchCollection matches =Regex.Matches(input, "".*?"");
foreach (Match match in matches){Console.WriteLine(match.Value);}
ただし、区切り文字の内部に同じ文字が入らないことが分かっている場合は、否定文字クラスのほうが明確です。
"[^"]"5. Regex.IsMatchを使った入力値の判定例
5-1. 半角数字だけで構成されているか判定する
半角数字だけで構成されているかは、[0-9]を使用して判定します。
static bool IsAsciiDigits(string? input){return input is not null &&Regex.IsMatch(input, @"\A[0-9]+\z");}使用例は次のとおりです。
Console.WriteLine(IsAsciiDigits("12345")); // TrueConsole.WriteLine(IsAsciiDigits("123")); // FalseConsole.WriteLine(IsAsciiDigits("12A")); // FalseConsole.WriteLine(IsAsciiDigits("")); // False空文字を許可する場合は、+をに変更します。
5-2. 半角英字・英数字を判定する
半角英字だけを許可する場合は、次のパターンを使用します。
bool lettersOnly =Regex.IsMatch("Hello", @"\A[A-Za-z]+\z");半角英数字だけを許可する場合は、次のようにします。
bool alphaNumeric =Regex.IsMatch("User123", @"\A[A-Za-z0-9]+\z");アンダースコアも許可する場合は追加します。
bool identifier =Regex.IsMatch("user_123", @"\A[A-Za-z0-9_]+\z");C#の識別子を完全に再現するにはUnicodeやキーワードの考慮が必要になるため、単純な英数字判定とは分けて考えます。
5-3. ひらがな・カタカナ・漢字を判定する
一般的なひらがなの範囲は、次のように指定できます。
bool hiragana =Regex.IsMatch("こんにちは", @"\A[\u3041-\u309F]+\z");一般的な全角カタカナの範囲は、次のとおりです。
bool katakana =Regex.IsMatch("コンニチハ", @"\A[\u30A1-\u30FF]+\z");漢字の基本的な範囲は、Unicodeプロパティを利用できます。
bool kanji =Regex.IsMatch("東京都", @"\A\p{IsCJKUnifiedIdeographs}+\z");ただし、日本語には長音記号、繰り返し記号、半角カタカナ、結合文字、拡張漢字などがあります。「日本語として入力できる文字」を正確に定義する場合は、業務要件に合わせて許可文字を決める必要があります。
5-4. 郵便番号の形式を判定する
日本の郵便番号を123-4567形式で判定する例です。
static bool IsPostalCode(string? input){return input is not null &&Regex.IsMatch(input, @"\A[0-9]{3}-[0-9]{4}\z");}Console.WriteLine(IsPostalCode("100-0001")); // TrueConsole.WriteLine(IsPostalCode("1000001")); // Falseハイフンなしも許可する場合は、次のようにします。
\A[0-9]{3}-?[0-9]{4}\zこの判定で確認できるのは形式だけです。実在する郵便番号かどうかは、郵便番号データとの照合が必要です。
5-5. 電話番号の形式を判定する
日本国内の電話番号には、固定電話、携帯電話、フリーダイヤルなど複数の形式があります。用途を限定したパターンを作ることが重要です。
携帯電話番号をハイフン付きで判定する簡単な例です。
string pattern = @"\A0[789]0-[0-9]{4}-[0-9]{4}\z";Console.WriteLine(Regex.IsMatch("090-1234-5678", pattern)); // True
ハイフンあり・なしを許可しつつ、区切りの有無を統一したい場合は、選択肢を分けると安全です。
string pattern =@"\A(?:0[789]0-[0-9]{4}-[0-9]{4}|0[789]0[0-9]{8})\z";電話番号は番号体系が複雑なため、すべてを単一の正規表現で厳密に検証しようとすると保守しにくくなります。
5-6. メールアドレスの形式を判定する
実用上の簡易チェックとして、次のようなパターンを使用できます。
static bool LooksLikeEmail(string? input){if (string.IsNullOrWhiteSpace(input)){return false;}return Regex.IsMatch(input,@"\A<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;">[^@\s]</span>+@<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;">[^@\s]</span>+\.<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;">[^@\s]</span>+\z",RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));
}
このパターンは、次の最低限の条件を確認します。
@の前後に文字があるドメイン部分にピリオドがある
空白を含まない
メールアドレスの仕様を完全な正規表現で再現すると非常に複雑になります。アカウント登録では、簡易的な形式チェックに加えて確認メールを送信し、実際に受信できることを確認する方法が現実的です。
5-7. URLの形式を判定する
URLは、単純な正規表現よりもUri.TryCreateを利用するほうが適しています。
static bool IsHttpUrl(string? input){return Uri.TryCreate(input, UriKind.Absolute, out Uri? uri) &&(uri.Scheme == Uri.UriSchemeHttp ||uri.Scheme == Uri.UriSchemeHttps);}正規表現で簡易的に判定する場合は、次のように記述できます。
string pattern = @"\Ahttps?://[^\s]+\z";Console.WriteLine(Regex.IsMatch("https://example.com/path", pattern)); // True
ただし、この正規表現はURLの構造やドメインの妥当性まで厳密に検証しません。
5-8. 日付や時刻の形式を判定する
yyyy-MM-dd形式を確認するだけなら、次のパターンを使用できます。
string pattern =@"\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z";しかし、2026-02-31も形式上は一致します。実在する日付か確認する場合は、DateTime.TryParseExactを使用します。
using System.Globalization;static bool IsValidDate(string input){return DateTime.TryParseExact(input,"yyyy-MM-dd",CultureInfo.InvariantCulture,DateTimeStyles.None,out _);}
時刻も同様です。
static bool IsValidTime(string input){return TimeSpan.TryParseExact(input,@"hh:mm",CultureInfo.InvariantCulture,out _);}正規表現は形式の大まかな確認に使い、値の妥当性は専用APIで判定するのが基本です。
5-9. 完全一致で判定するためのパターン指定
入力値の全体がパターンに一致することを確認したい場合は、\Aと\zで囲みます。
string pattern = @"\A[A-Z]{2}[0-9]{4}\z";Console.WriteLine(Regex.IsMatch("AB1234", pattern)); // True
Console.WriteLine(Regex.IsMatch("ID:AB1234", pattern)); // False
次のパターンではアンカーがないため、部分一致になります。
string pattern = @"[A-Z]{2}[0-9]{4}";Console.WriteLine(Regex.IsMatch("ID:AB1234", pattern)); // True
ユーザー入力の形式判定では、部分一致と完全一致を混同しないようにしましょう。
6. Regex.Match・Matchesを使った文字列検索と抽出
6-1. 一致した文字列をValueプロパティで取得する
Match.Valueには、正規表現全体に一致した文字列が格納されます。
string input = "受付番号: ABC-12345";Match match =Regex.Match(input, @"[A-Z]{3}-[0-9]{5}");
if (match.Success){Console.WriteLine(match.Value); // ABC-12345}
一致しなかった場合は、Valueを使用する前にSuccessを確認します。
6-2. 一致位置と文字数をIndex・Lengthで取得する
Indexは一致開始位置、Lengthは一致した文字数です。
string input = "価格は1,280円です。";Match match =Regex.Match(input, @"[0-9,]+");
if (match.Success){Console.WriteLine(match.Value); // 1,280Console.WriteLine(match.Index); // 3Console.WriteLine(match.Length); // 5}
一致箇所の前後を取得することもできます。
string before = input[..match.Index];string after = input[(match.Index + match.Length)..];
6-3. 複数の一致結果を順番に処理する
Regex.Matchesで取得した結果は、出現順に処理できます。
string input ="ID=A-100, ID=B-200, ID=C-300";MatchCollection matches =Regex.Matches(input, @"[A-Z]-[0-9]{3}");
foreach (Match match in matches){Console.WriteLine($"{match.Value}: {match.Index}");}
Match.NextMatchを使って順番に取得する方法もあります。
Regex regex = new(@"[A-Z]-[0-9]{3}");Match match = regex.Match(input);while (match.Success){Console.WriteLine(match.Value);match = match.NextMatch();}
6-4. グループ化した部分をGroupsで取得する
括弧で囲んだ部分は、Groupsから取得できます。
string input = "2026-06-20";Match match = Regex.Match(input,@"\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z");
if (match.Success){Console.WriteLine(match.Groups[0].Value); // 2026-06-20Console.WriteLine(match.Groups[1].Value); // 2026Console.WriteLine(match.Groups[2].Value); // 06Console.WriteLine(match.Groups[3].Value); // 20}
Groups[0]は正規表現全体の一致です。Groups[1]以降が、丸括弧でキャプチャした結果になります。
6-5. 名前付きグループで抽出結果を分かりやすくする
番号でグループを参照すると、パターン変更時に読みづらくなることがあります。名前付きグループを使うと、意味が分かりやすくなります。
string pattern =@"\A(?<year>[0-9]{4})-" +@"(?<month>[0-9]{2})-" +@"(?<day>[0-9]{2})\z";Match match =Regex.Match("2026-06-20", pattern);
if (match.Success){Console.WriteLine(match.Groups["year"].Value);Console.WriteLine(match.Groups["month"].Value);Console.WriteLine(match.Groups["day"].Value);}
名前付きグループは次の形式で記述します。
(?<グループ名>パターン)6-6. CapturesとGroupsの違い
Groupsは各キャプチャグループの結果を表します。一方、同じキャプチャグループが繰り返された場合、Capturesには各回のキャプチャ結果が格納されます。
Match match =Regex.Match("1234", @"([0-9])+");Group group = match.Groups[1];
Console.WriteLine(group.Value); // 4
foreach (Capture capture in group.Captures){Console.WriteLine(capture.Value);}
実行結果は次のとおりです。
1234Group.Valueには最後にキャプチャした4が入りますが、Capturesには繰り返しごとの結果が入ります。
繰り返し全体を取得したいだけなら、量指定子をグループの内側に置きます。
([0-9]+)6-7. ログや文章から必要な情報を抽出する実例
次の形式のログを考えます。
2026-06-20 14:30:15 [ERROR] Database connection failed日時、レベル、メッセージを名前付きグループで抽出します。
string log ="2026-06-20 14:30:15 [ERROR] " +"Database connection failed";string pattern =@"\A(?<date>[0-9]{4}-[0-9]{2}-[0-9]{2}) " +@"(?<time>[0-9]{2}:[0-9]{2}:[0-9]{2}) " +@"[(?<level>[A-Z]+)] " +@"(?<message>.*)\z";
Match match = Regex.Match(log, pattern);
if (match.Success){Console.WriteLine(match.Groups["date"].Value);Console.WriteLine(match.Groups["time"].Value);Console.WriteLine(match.Groups["level"].Value);Console.WriteLine(match.Groups["message"].Value);}
ログ形式が固定されている場合、名前付きグループを利用すると後続処理を書きやすくなります。
7. Regex.Replaceを使った文字列置換
7-1. 一致した文字列を固定文字列に置換する
一致した部分を固定文字列に置き換える基本例です。
string input ="エラーコードはE100です。";string result = Regex.Replace(input,@"E[0-9]{3}","UNKNOWN");
Console.WriteLine(result);// エラーコードはUNKNOWNです。
すべての一致箇所が置換されます。
7-2. 一致した文字列を削除する
置換後の文字列にstring.Emptyを指定すると、一致部分を削除できます。
string input = "A-1-B-2-C-3";string result = Regex.Replace(input,@"[0-9-]",string.Empty);
Console.WriteLine(result); // ABC
数字だけを削除する場合は次のとおりです。
string result =Regex.Replace("A1B2C3", @"[0-9]", "");7-3. 複数の空白や改行を整形する
連続する半角スペースを1つにまとめる例です。
string input ="C# regular expression";string result =Regex.Replace(input, @" +", " ");
Console.WriteLine(result);// C# regular expression
タブや改行を含む空白文字全般をまとめる場合は、\s+を使用します。
string input = "C#\t正規表現\n入門";string result =Regex.Replace(input, @"\s+", " ");
Console.WriteLine(result);// C# 正規表現 入門
行構造を残したい場合は、改行と水平方向の空白を分けて処理します。
string result =Regex.Replace(input, @"[^\S\r\n]+", " ");7-4. キャプチャグループを置換後の文字列で再利用する
置換文字列では、$1、$2のようにキャプチャグループを参照できます。
string input = "2026-06-20";string result = Regex.Replace(input,@"\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z","$1/$2/$3");
Console.WriteLine(result); // 2026/06/20
姓と名の順序を入れ替える例です。
string input = "Yamada Taro";string result = Regex.Replace(input,@"\A(\w+)\s+(\w+)\z","$2 $1");
Console.WriteLine(result); // Taro Yamada
7-5. 名前付きグループを使って置換する
名前付きグループは、置換文字列から${name}形式で参照できます。
string input = "2026-06-20";string result = Regex.Replace(input,@"\A(?<year>[0-9]{4})-" +@"(?<month>[0-9]{2})-" +@"(?<day>[0-9]{2})\z","${year}年${month}月${day}日");
Console.WriteLine(result);// 2026年06月20日
グループ数が多い処理では、番号より名前を使用したほうが保守しやすくなります。
7-6. MatchEvaluatorで一致内容に応じて動的に置換する
一致した内容を計算して置換したい場合は、MatchEvaluatorに相当するラムダ式を渡します。
string input = "価格: 100円、200円、300円";string result = Regex.Replace(input,@"[0-9]+",match =>{int value = int.Parse(match.Value);return (value * 2).ToString();});
Console.WriteLine(result);// 価格: 200円、400円、600円
大文字と小文字を反転する例です。
string result = Regex.Replace("AbC",@"[A-Za-z]",match =>char.IsUpper(match.Value[0])? match.Value.ToLowerInvariant(): match.Value.ToUpperInvariant());Console.WriteLine(result); // aBc
7-7. 個人情報の一部をマスキングする実例
電話番号の中央4桁をマスキングする例です。
string input = "連絡先: 090-1234-5678";string result = Regex.Replace(input,@"(?<=\A|[^0-9])" +@"(?<head>0[789]0)-" +@"[0-9]{4}-" +@"(?<tail>[0-9]{4})","${head}-****-${tail}");
Console.WriteLine(result);// 連絡先: 090-****-5678
メールアドレスのローカル部を一部隠す例です。
string email = "taro.yamada@example.com";string masked = Regex.Replace(email,@"\A(?<first>.)(?<middle>.)(?<at>@.+)\z",match =>{string middle =new('', match.Groups["middle"].Length);
return 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;">["first"]</span>.Value +middle +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;">["at"]</span>.Value;});
Console.WriteLine(masked);// t**********@example.com
実際のマスキングでは、ログやエラー出力に元データを残さない設計も重要です。
8. C#特有の正規表現文字列とエスケープの注意点
8-1. 通常の文字列リテラルでバックスラッシュを記述する方法
C#の通常文字列では、バックスラッシュ自体を\と記述します。
正規表現の\dを通常文字列で表す場合は、次のようになります。
string pattern = "\A\d+\z";正規表現エンジンに渡される実際のパターンは次のとおりです。
\A\d+\z正規表現のエスケープとC#文字列のエスケープが重なるため、バックスラッシュが多いパターンは読みづらくなります。
8-2. 逐語的文字列リテラル「@」を使う方法
文字列の先頭に@を付ける逐語的文字列リテラルでは、バックスラッシュをそのまま記述できます。
string pattern = @"\A\d+\z";正規表現パターンではバックスラッシュを頻繁に使うため、C#では逐語的文字列リテラルがよく利用されます。
Windowsパスも読みやすく記述できます。
string pattern =@"\AC:\Users\[^\]+\Documents\z";ただし、逐語的文字列でも正規表現側で必要なエスケープは省略できません。
8-3. 通常文字列と逐語的文字列のパターン比較
次の2つは同じ正規表現です。
string normal ="\A[0-9]{3}-[0-9]{4}\z";string verbatim =@"\A[0-9]{3}-[0-9]{4}\z";
通常文字列では、正規表現のバックスラッシュをC#側でさらにエスケープします。
逐語的文字列では、正規表現のパターンに近い見た目で記述できます。そのため、特別な理由がなければ、正規表現には逐語的文字列を使うと読みやすくなります。
8-4. ダブルクォーテーションを含むパターンの書き方
通常文字列でダブルクォーテーションを含める場合は、"と記述します。
string pattern = ""[^"]"";逐語的文字列では、ダブルクォーテーションを2つ重ねます。
string pattern = @"""[^""]""";C# 11以降のraw文字列リテラルを使用すると、さらに読みやすくなる場合があります。
string pattern = """ "[^"]" """.Trim();実際には余計な空白を避けるため、次のような形が分かりやすいでしょう。
string pattern = """"[^"]"""";使用するC#の言語バージョンに応じて、チーム内で記述方法を統一します。
8-5. Regex.Escapeで入力文字列を安全にエスケープする
ユーザー入力や設定値を正規表現の一部に組み込む場合、その文字列にメタ文字が含まれている可能性があります。
string keyword = "C++";string input = "C++ and C#";string pattern = Regex.Escape(keyword);
bool result = Regex.IsMatch(input, pattern);
Console.WriteLine(result); // True
Regex.Escape("C++")は、+を正規表現の量指定子ではなく通常の文字として扱える形に変換します。
前後にパターンを追加する場合も、外部入力の部分だけをエスケープします。
string keyword = "file.txt";string pattern =$@"\A.{Regex.Escape(keyword)}.\z";
8-6. Regex.Unescapeを使用するときの注意点
Regex.Unescapeは、エスケープされた文字列を復元するためのメソッドです。
string escaped = @"C++";string unescaped = Regex.Unescape(escaped);Console.WriteLine(unescaped); // C++
ただし、Regex.Unescapeは任意の文字列を安全にするメソッドではありません。また、すべての入力についてRegex.Escapeの完全な逆変換として利用できると決めつけないほうが安全です。
ユーザー入力の検証、HTMLエスケープ、URLデコード、JSONデコードなどの代わりにはなりません。目的に合った専用のエンコード・デコード処理を使用してください。
9. RegexOptionsで検索条件を設定する方法
9-1. IgnoreCaseで大文字と小文字を区別しない
RegexOptions.IgnoreCaseを指定すると、大文字と小文字を区別せずに検索できます。
bool result = Regex.IsMatch("Hello CSharp",@"csharp",RegexOptions.IgnoreCase);Console.WriteLine(result); // True
英字の識別子やプロトコル名を処理する場合は、CultureInvariantも組み合わせることがあります。
RegexOptions options =RegexOptions.IgnoreCase |RegexOptions.CultureInvariant;9-2. Multilineで「^」「$」を行単位に適用する
RegexOptions.Multilineを指定すると、^と$が各行の先頭と末尾に一致します。
string input = """INFO StartedERROR FailedINFO Finished""";MatchCollection matches = Regex.Matches(input,@"^ERROR.*$",RegexOptions.Multiline);
foreach (Match match in matches){Console.WriteLine(match.Value);}
\Aと\zは、Multilineを指定しても文字列全体の先頭と末尾を表します。
9-3. Singlelineで「.」を改行にも一致させる
RegexOptions.Singlelineを指定すると、.が改行にも一致します。
string input = """BEGINline 1line 2END""";Match match = Regex.Match(input,@"BEGIN.*END",RegexOptions.Singleline);
Console.WriteLine(match.Success); // True
名前から誤解しやすいですが、Singlelineが変更するのは.の動作です。^と$の行単位の動作はMultilineで設定します。
9-4. IgnorePatternWhitespaceでパターンを読みやすくする
RegexOptions.IgnorePatternWhitespaceを指定すると、パターン内の多くの空白や#から行末までのコメントを無視できます。
string pattern = """\A(?<year> [0-9]{4} ) # 年-(?<month> [0-9]{2} ) # 月-(?<day> [0-9]{2} ) # 日\z""";Match match = Regex.Match("2026-06-20",pattern,RegexOptions.IgnorePatternWhitespace);
空白そのものに一致させたい場合は、\s、\x20、または文字クラス内の空白を使用します。
[ ]文字クラス内部の空白は、通常は無視されません。
9-5. CultureInvariantでカルチャ依存の動作を避ける
RegexOptions.CultureInvariantは、カルチャに依存しない方法で文字比較を行うためのオプションです。
特にIgnoreCaseを使用して機械的な識別子、プロトコル、設定値などを処理するときに検討します。
Regex regex = new(@"\Aadmin\z",RegexOptions.IgnoreCase |RegexOptions.CultureInvariant);ユーザー向けの自然言語を現在のカルチャに合わせて処理したい場合は、要件を確認して選択します。
9-6. Compiledを指定するメリットと注意点
RegexOptions.Compiledを指定すると、正規表現を実行用コードにコンパイルします。
Regex regex = new(@"\A[0-9]{3}-[0-9]{4}\z",RegexOptions.Compiled,TimeSpan.FromMilliseconds(200));同じ複雑なパターンを大量に実行する処理では、実行速度が改善する可能性があります。
一方で、次のコストがあります。
初期化に時間がかかる
追加のメモリを使用する
数回しか使わないパターンでは利点が出にくい
実行環境やパターンによって効果が異なる
最初からすべてにCompiledを指定するのではなく、計測結果に基づいて選択します。現在の.NETでは、ビルド時にコードを生成するGeneratedRegexも有力な選択肢です。
9-7. 複数のRegexOptionsを組み合わせる方法
複数のオプションは、ビットOR演算子|で組み合わせます。
RegexOptions options =RegexOptions.IgnoreCase |RegexOptions.Multiline |RegexOptions.CultureInvariant;MatchCollection matches = Regex.Matches(input,@"^error.$",options);
オプションはコンストラクターやstaticメソッドの引数で指定できます。
パターン内にインラインオプションを記述する方法もあります。
(?im)^error.$一部だけに適用する場合は、次の形式を使用します。
(?i:error)10. グループ・先読み・後読みを使った応用パターン
10-1. キャプチャするグループとキャプチャしないグループ
通常の丸括弧は、グループ化とキャプチャの両方を行います。
(ab)+結果を取得する必要がない場合は、非キャプチャグループを使います。
(?:ab)+例として、画像拡張子を判定します。
string pattern =@"\A.+.(?:jpg|jpeg|png|gif)\z";bool result = Regex.IsMatch("photo.png",pattern,RegexOptions.IgnoreCase);
非キャプチャグループを使うと、不要なGroupsが増えず、パターンの意図も分かりやすくなります。
10-2. 肯定先読みで後続文字を条件にする
肯定先読み(?=...)は、後ろに指定したパターンが続くことを条件にします。条件部分は一致結果に含まれません。
数字の後ろに「円」がある場合だけ数字を取得する例です。
string input = "価格は1200円、在庫は30個です。";MatchCollection matches =Regex.Matches(input, @"[0-9]+(?=円)");
foreach (Match match in matches){Console.WriteLine(match.Value); // 1200}
パスワードに少なくとも1つの数字が含まれることを確認する例です。
(?=.*[0-9])先読みは、複数の条件を同時に判定する場合によく利用されます。
10-3. 否定先読みで特定パターンを除外する
否定先読み(?!...)は、後ろに指定したパターンが続かないことを条件にします。
testで始まらない英単語を判定する例です。
string pattern =@"\A(?!test)[A-Za-z]+\z";Console.WriteLine(Regex.IsMatch("sample", pattern)); // True
Console.WriteLine(Regex.IsMatch("testing", pattern)); // False
特定の拡張子を除外する例です。
\A(?!.*.tmp\z).+.[A-Za-z0-9]+\z除外条件が増えすぎる場合は、正規表現の外で条件分岐したほうが読みやすいこともあります。
10-4. 肯定後読みで直前の文字を条件にする
肯定後読み(?<=...)は、直前に指定したパターンがあることを条件にします。
通貨記号の後ろにある数字だけを取得する例です。
string input = "価格は¥1200、個数は30です。";MatchCollection matches =Regex.Matches(input, @"(?<=¥)[0-9]+");
foreach (Match match in matches){Console.WriteLine(match.Value); // 1200}
¥は一致結果に含まれません。
10-5. 否定後読みで直前のパターンを除外する
否定後読み(?<!...)は、直前に指定したパターンがないことを条件にします。
バックスラッシュでエスケープされていないダブルクォーテーションを探す簡単な例です。
(?<!\)"マイナス記号の直後ではない数字を取得する例です。
string input = "10 -20 30";MatchCollection matches =Regex.Matches(input, @"(?<!-)\b[0-9]+\b");
後読みの条件は複雑にしすぎると読みづらくなります。前後の文字をグループで取得して、C#側で判断する方法も検討します。
10-6. 後方参照で同じ文字列の繰り返しを検出する
後方参照は、先にキャプチャした文字列と同じ内容に一致させる機能です。
連続して重複する単語を検出する例です。
string input ="This is is a test test sentence.";string pattern =@"\b(?<word>[A-Za-z]+)\s+\k<word>\b";
MatchCollection matches = Regex.Matches(input,pattern,RegexOptions.IgnoreCase);
foreach (Match match in matches){Console.WriteLine(match.Value);}
番号付きグループでは、\1のように参照できます。
\b([A-Za-z]+)\s+\1\b名前付きグループのほうが、パターン変更時に参照関係を理解しやすくなります。
10-7. 条件付きグループとアトミックグループの使いどころ
条件付きグループは、特定のグループが一致したかどうかによって後続パターンを切り替えます。
(?(name)yes-pattern|no-pattern)括弧で囲まれた値、または括弧なしの値を扱う例です。
string pattern =@"\A(?<open>()?[^()]+" +@"(?(open)))\z";Console.WriteLine(Regex.IsMatch("(value)", pattern)); // True
Console.WriteLine(Regex.IsMatch("value", pattern)); // True
Console.WriteLine(Regex.IsMatch("(value", pattern)); // False
アトミックグループ(?>...)は、一度一致したグループ内部へのバックトラッキングを禁止します。
(?>[0-9]+)不要なバックトラッキングを減らせる一方、通常なら成功する一致が失敗する場合があります。動作を理解し、テストしたうえで使用します。
11. C#正規表現で初心者がつまずきやすい原因と対処法
11-1. バックスラッシュのエスケープ漏れで一致しない
通常文字列で次のように書くと、C#側で不正なエスケープとして扱われます。
// string pattern = "\d+";通常文字列では、バックスラッシュを重ねます。
string pattern = "\d+";または、逐語的文字列を使います。
string pattern = @"\d+";正規表現では逐語的文字列を基本にすると、エスケープミスを減らせます。
11-2. 「^」「$」を付けず部分一致になっている
次の判定は、文字列の一部に数字があればtrueです。
bool result =Regex.IsMatch("abc123", @"[0-9]+");Console.WriteLine(result); // True
数字だけで構成されていることを確認する場合は、完全一致にします。
bool result =Regex.IsMatch("abc123", @"\A[0-9]+\z");Console.WriteLine(result); // False
入力値の検証では、アンカーの付け忘れを最初に確認しましょう。
11-3. 「.」や「」の範囲が広すぎて想定外に一致する
次のパターンは、最初の開始タグから最後の終了タグまで一致します。
<tag>.</tag>最短一致にする方法があります。
<tag>.?</tag>内部に<を含まないことが分かっているなら、否定文字クラスのほうが範囲を限定できます。
<tag>[^<]</tag>「何でもよい」を表す.や.*を使用する前に、許可する文字を具体的に指定できないか検討します。
11-4. 全角文字と半角文字を同じものとして扱っている
次のパターンは半角数字だけに一致します。
[0-9]+一方、全角数字を含むUnicodeの10進数字にも一致させたい場合は、\dや\p{Nd}を使用します。
\d+Console.WriteLine(Regex.IsMatch("123", @"\A\d+\z")); // Trueの可能性半角と全角を同一視したい場合は、判定前にUnicode正規化や全角・半角変換を行う設計もあります。ただし、勝手に変換してよいかは業務要件によって異なります。
11-5. 改行コードの違いでパターンが一致しない
環境によって、改行コードは主に次の形式があります。
Windows:
\r\nLinux系:
\n古い形式など:
\r
改行を直接指定する場合は、複数形式を考慮できます。
\r\n|\n|\rC#で行分割する例です。
string[] lines =Regex.Split(input, @"\r\n|\n|\r");単に任意の改行シーケンスを扱う場合は、\r?\nだけでは単独の\rを処理できない点に注意します。
11-6. Match.Successを確認せず結果を参照している
Regex.Matchは、一致しなかった場合もMatchオブジェクトを返します。
Match match =Regex.Match("abc", @"[0-9]+");Console.WriteLine(match.Value); // 空文字
一致結果を使用する前に、Successを確認します。
if (!match.Success){Console.WriteLine("一致しませんでした。");return;}Console.WriteLine(match.Value);
Groupsを参照する場合も、正規表現全体または対象グループのSuccessを確認すると安全です。
11-7. nullと空文字を考慮していない
Regexの入力文字列にnullを渡すと例外が発生します。外部入力を処理する場合は、先にnullを確認します。
static bool IsProductCode(string? input){if (string.IsNullOrEmpty(input)){return false;}return Regex.IsMatch(input,@"\A<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;">[A-Z]</span>{2}-<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;">[0-9]</span>{4}\z");
}
空白だけの入力も無効にする場合は、string.IsNullOrWhiteSpaceを使用します。
if (string.IsNullOrWhiteSpace(input)){return false;}11-8. 正規表現だけで厳密な入力検証を完結させている
正規表現で確認できるのは、主に文字列の形式です。
例えば、次の値はyyyy-MM-ddという形式には合っています。
2026-02-31しかし、実在する日付ではありません。
同様に、次のような意味上の妥当性は別の処理が必要です。
日付が実在するか
URLとして解析できるか
メールを実際に受信できるか
郵便番号が実在するか
電話番号が割り当て済みか
数値が許容範囲内か
正規表現による形式チェックの後に、TryParse、データベース照合、確認メールなどを組み合わせます。
12. 実務で使えるC#正規表現のサンプル集
12-1. 文字列から数字だけを抽出する
一致した数字をすべて連結する方法です。
string input ="電話番号: 090-1234-5678";string digits = string.Concat(Regex.Matches(input, @"[0-9]").Cast<Match>().Select(match => match.Value));
Console.WriteLine(digits);// 09012345678
数字以外を削除するほうが簡潔です。
string digits =Regex.Replace(input, @"[^0-9]", "");連続した数値を個別に取得する場合は、Matchesを使用します。
string[] numbers =Regex.Matches("A12 B345 C6", @"[0-9]+").Cast<Match>().Select(match => match.Value).ToArray();12-2. HTMLタグを検出・除去する
単純なHTMLタグらしい文字列を検出する例です。
string html ="<p>Hello <strong>world</strong></p>";MatchCollection tags =Regex.Matches(html, @"<[^>]+>");
タグを削除する簡易例です。
string text =Regex.Replace(html, @"<[^>]+>", "");Console.WriteLine(text);// Hello world
ただし、HTMLは入れ子、コメント、スクリプト、属性値、壊れたマークアップなどを含む複雑な構文です。正規表現だけで正確に解析・無害化することは困難です。
実際のHTML解析やサニタイズには、目的に合ったHTMLパーサーやサニタイザーを使用してください。
12-3. CSVや区切り文字を考慮して文字列を分割する
引用符を考慮しない単純なCSVなら、string.Splitで十分です。
string[] values ="A,B,C".Split(',');ダブルクォーテーション内のカンマを分割しない簡易パターンは、次のように記述できます。
string input ="""A,"B,C",D""";string[] values = Regex.Split(input,""",(?=(?:[^"]"[^"]")[^"]$)""");
ただし、正式なCSVには次のような要素があります。
""による引用符のエスケープ引用符内の改行
空フィールド
末尾の区切り文字
文字コードや方言の違い
業務データを扱う場合は、CSV対応ライブラリや専用パーサーを使用するほうが安全です。
12-4. ファイル名から拡張子を取得する
正規表現で末尾の拡張子を取得する例です。
string fileName = "report.final.pdf";Match match = Regex.Match(fileName,@"(?<extension>.[^./\]+)\z");
if (match.Success){Console.WriteLine(match.Groups["extension"].Value);// .pdf}
ただし、ファイルパスの処理にはSystem.IO.Pathを使うほうが適切です。
using System.IO;string extension =Path.GetExtension("report.final.pdf");
Console.WriteLine(extension); // .pdf
正規表現は、通常のパスAPIでは表現しにくい特殊な命名規則を確認する場合に使います。
12-5. ログから日時・レベル・メッセージを抽出する
複数行のログを解析する例です。
string input = """2026-06-20 10:15:00 [INFO] Started2026-06-20 10:15:03 [ERROR] Failed""";string pattern =@"^(?<timestamp>" +@"[0-9]{4}-[0-9]{2}-[0-9]{2} " +@"[0-9]{2}:[0-9]{2}:[0-9]{2}) " +@"[(?<level>[A-Z]+)] " +@"(?<message>.*)$";
MatchCollection matches = Regex.Matches(input,pattern,RegexOptions.Multiline);
foreach (Match match in matches){string timestamp =match.Groups["timestamp"].Value;
string level =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;">["level"]</span>.Value;string message =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;">["message"]</span>.Value;Console.WriteLine($"{timestamp} / {level} / {message}");
}
ログの日時は、抽出後にDateTime.TryParseExactで解析すると安全です。
12-6. 重複する単語や連続した空白を検出する
連続して重複する英単語を検出します。
string input ="This is is a sample sample text.";MatchCollection duplicates = Regex.Matches(input,@"\b(?<word>[A-Za-z]+)" +@"\s+\k<word>\b",RegexOptions.IgnoreCase);
foreach (Match match in duplicates){Console.WriteLine(match.Value);}
2文字以上の連続空白を検出する例です。
bool hasRepeatedSpaces =Regex.IsMatch(input, @" {2,}");連続する空白文字全般を1つにまとめる場合は、次のようにします。
string normalized =Regex.Replace(input, @"\s+", " ");12-7. パスワードの文字種と文字数を判定する
8文字以上64文字以下で、半角英小文字・大文字・数字をそれぞれ1文字以上含む例です。
static bool IsValidPassword(string? password){if (password is null){return false;}string pattern =@"\A" +@"(?=.*<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;">[a-z]</span>)" +@"(?=.*<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;">[A-Z]</span>)" +@"(?=.*<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;">[0-9]</span>)" +@"<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;">[A-Za-z0-9!@#$%^&*]</span>{8,64}" +@"\z";return Regex.IsMatch(password,pattern,RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));
}
記号を1文字以上必須にする場合は、先読みを追加します。
(?=.[!@#$%^&])ただし、利用できる文字を過度に制限すると、長いパスフレーズやパスワードマネージャーで生成した文字列を拒否してしまいます。セキュリティ要件に合わせて設計してください。
12-8. 特定のキーワードを含む行だけ抽出する
ERRORを含む行だけ取得する例です。
string input = """INFO Application startedWARN Memory usage is highERROR Database unavailableINFO Application stopped""";MatchCollection matches = Regex.Matches(input,@"^.ERROR.$",RegexOptions.Multiline);
foreach (Match match in matches){Console.WriteLine(match.Value);}
大文字と小文字を区別しない場合は、IgnoreCaseを追加します。
RegexOptions options =RegexOptions.Multiline |RegexOptions.IgnoreCase |RegexOptions.CultureInvariant;固定キーワードを含む行を処理するだけなら、行ごとに分割してContainsを使う方法も読みやすいでしょう。
13. 正規表現を安全かつ高速に使うためのポイント
13-1. Regexのタイムアウトを設定する
正規表現は、パターンと入力内容の組み合わせによって処理時間が大幅に増える場合があります。特に外部から受け取った文字列を処理する場合は、タイムアウトを設定します。
Regex regex = new(@"\A[A-Za-z0-9]+\z",RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));try{bool result = regex.IsMatch(input);}catch (RegexMatchTimeoutException){Console.WriteLine("正規表現の処理がタイムアウトしました。");}
staticメソッドでもタイムアウトを指定できます。
bool result = Regex.IsMatch(input,pattern,RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));バックトラッキングを利用する正規表現や信頼できない入力を扱う場合、タイムアウトの指定が推奨されています。
13-2. ReDoSを引き起こしやすいパターンを避ける
ReDoSは、正規表現の処理を意図的に長時間化させ、サービスを利用不能にする攻撃です。
例えば、曖昧な繰り返しが入れ子になったパターンは注意が必要です。
^(a+)+$このパターンに、多数のaの後ろへ不一致文字を追加した入力を与えると、大量のバックトラッキングが発生する可能性があります。
aaaaaaaaaaaaaaaaaaaaaaaa!対策として、次の点を意識します。
量指定子を入れ子にしない
.の範囲を狭くする選択肢の共通部分や曖昧さを減らす
入力文字数に上限を設ける
タイムアウトを設定する
利用可能なら
RegexOptions.NonBacktrackingを検討する
13-3. 不要なバックトラッキングを減らす
パターンを具体的にすると、正規表現エンジンが試す候補を減らせます。
広すぎるパターンは次のとおりです。
.@.空白と@を除外することで、範囲を限定できます。
[^@\s]+@[^@\s]+終端が決まっている場合も、.より否定文字クラスが適することがあります。
"[^"]".NETの対応環境では、バックトラッキングを行わず、入力長に対して線形時間の処理を目指すRegexOptions.NonBacktrackingも利用できます。
Regex regex = new(@"\A[A-Za-z0-9]+\z",RegexOptions.NonBacktracking |RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));ただし、後方参照や先読みなど、一部の正規表現機能とは併用できません。
13-4. Regexインスタンスを再利用する
同じパターンを何度も利用する場合は、毎回Regexを生成せず、インスタンスを再利用します。
public static class Validators{private static readonly Regex PostalCodeRegex =new(@"\A[0-9]{3}-[0-9]{4}\z",RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));public static bool IsPostalCode(string? input){return input is not null &&PostalCodeRegex.IsMatch(input);}
}
Regexオブジェクトは不変で、読み取り目的のマッチング処理では複数箇所から再利用できます。
ただし、パフォーマンス改善は推測せず、実際の負荷で計測することが重要です。
13-5. GeneratedRegexを利用する
.NET 7以降では、GeneratedRegex属性を使って、正規表現の実装をビルド時に生成できます。
using System.Text.RegularExpressions;public static partial class Patterns{[GeneratedRegex(@"\A[0-9]{3}-[0-9]{4}\z",RegexOptions.CultureInvariant,200)]public static partial Regex PostalCode();}
使用例は次のとおりです。
bool result =Patterns.PostalCode().IsMatch("100-0001");GeneratedRegexには、次の利点があります。
正規表現をビルド時に生成できる
インスタンスがキャッシュされる
パターンの実装をソースとして確認しやすい
AOT環境との相性を改善できる
実行時コンパイルのコストを避けられる
一方で、生成コードによってアセンブリサイズが増える可能性があります。繰り返し利用する既知のパターンで検討するとよいでしょう。
13-6. 正規表現より文字列メソッドが適しているケース
次のような処理では、正規表現を使わないほうが簡潔です。
固定文字列を含むか確認する場合:
bool result =input.Contains("ERROR",StringComparison.Ordinal);接頭辞を確認する場合:
bool result =input.StartsWith("https://",StringComparison.OrdinalIgnoreCase);末尾を確認する場合:
bool result =input.EndsWith(".txt",StringComparison.OrdinalIgnoreCase);固定文字で分割する場合:
string[] values = input.Split(',');文字を置換する場合:
string result = input.Replace("-", "");正規表現は強力ですが、常に最適とは限りません。読みやすさ、性能、保守性を基準に選択します。
13-7. 複雑な正規表現をコメントとテストで管理する
複雑なパターンは、1行に詰め込まず、IgnorePatternWhitespaceとコメントを利用します。
string pattern = """\A(?<prefix> [A-Z]{2} ) # 種別コード-(?<number> [0-9]{6} ) # 連番-(?<check> [A-Z0-9] ) # チェック文字\z""";Regex regex = new(pattern,RegexOptions.IgnorePatternWhitespace |RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));
コードコメントには、次の内容を残すと保守しやすくなります。
何を判定するパターンか
許可する入力例
拒否する入力例
仕様上あえて許可・除外している文字
タイムアウト値の根拠
関連するテストコード
14. C#正規表現のデバッグとテスト方法
14-1. パターンを小さな単位に分けて確認する
複雑な正規表現を一度に作ると、どこで失敗しているか分かりにくくなります。
例えば商品コードAB-123456-Xを判定する場合は、次の順序で確認します。
接頭辞だけを確認します。
[A-Z]{2}番号を追加します。
[A-Z]{2}-[0-9]{6}末尾のチェック文字を追加します。
[A-Z]{2}-[0-9]{6}-[A-Z0-9]最後に完全一致のアンカーを追加します。
\A[A-Z]{2}-[0-9]{6}-[A-Z0-9]\z段階的に作ると、問題のある部分を特定しやすくなります。
14-2. 一致結果とキャプチャ内容を出力して確認する
Valueだけでなく、Index、Length、Groupsも出力すると、正規表現の動作を確認しやすくなります。
static void PrintMatch(Match match){Console.WriteLine($"Success: {match.Success}");if (!match.Success){return;}Console.WriteLine($"Value: {match.Value}");Console.WriteLine($"Index: {match.Index}");Console.WriteLine($"Length: {match.Length}");for (int i = 0; i < match.Groups.Count; i++){Group group = 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;">[i]</span>;Console.WriteLine($"Group<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}");}
}
名前付きグループを確認する場合は、グループ名を明示します。
Console.WriteLine(match.Groups["year"].Value);14-3. 正常系・異常系・境界値のテストケースを用意する
郵便番号のパターンをテストする場合、正常な値だけでは不十分です。
正常系:
100-0001123-4567異常系:
100000112-34567ABC-DEFG100-000A境界値や見落としやすい入力:
空文字null前後に空白がある末尾に改行がある全角数字を含む非常に長い文字列正規表現のテストでは、「一致してほしい文字列」と「一致してほしくない文字列」の両方を用意します。
14-4. xUnitで正規表現の判定をテストする
xUnitのTheoryとInlineDataを使うと、複数の入力をまとめてテストできます。
using System.Text.RegularExpressions;using Xunit;public class PostalCodeTests{private static readonly Regex Regex =new(@"\A[0-9]{3}-[0-9]{4}\z",RegexOptions.CultureInvariant,TimeSpan.FromMilliseconds(200));
<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;">[Theory]</span><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;">[InlineData("100-0001", true)]</span><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;">[InlineData("123-4567", true)]</span><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;">[InlineData("1000001", false)]</span><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;">[InlineData("ABC-DEFG", false)]</span><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;">[InlineData("", false)]</span><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;">[InlineData("100-0001", false)]</span>public void IsMatch_ReturnsExpectedResult(string input,bool expected){bool actual = Regex.IsMatch(input);Assert.Equal(expected, actual);}
}
nullを含める場合は、引数をstring?にします。
[Theory][InlineData(null, false)]public void IsPostalCodeHandlesNull(string? input,bool expected){bool actual =input is not null &&Regex.IsMatch(input);Assert.Equal(expected, actual);
}
14-5. オンライン正規表現テスターを使う際の注意点
オンラインテスターは、パターンの試行や一致範囲の確認に便利です。
ただし、次の点に注意してください。
正規表現エンジンを.NETに設定する
機密情報や個人情報を貼り付けない
C#文字列リテラルのエスケープとの違いを確認する
オプション設定をC#コードと合わせる
オンラインで成功しても、実際の.NETテストを行う
大量データや悪意ある入力で性能を確認する
テスター上のパターンが\d+でも、C#の通常文字列では"\d+"、逐語的文字列では@"\d+"となります。
14-6. .NETと他言語の正規表現エンジンの違いを確認する
正規表現の基本記法は似ていますが、すべての言語やツールで同じ動作になるわけではありません。
違いが出やすい項目には、次のものがあります。
名前付きグループの記法
後読みの制約
Unicode文字クラス
条件付きグループ
バランシンググループ
インラインオプション
$の末尾改行に対する動作置換文字列でのグループ参照
非バックトラッキングモード
JavaScriptやPython向けのパターンをそのままC#へコピーせず、.NETの正規表現として動作確認してください。
15. C#正規表現に関するよくある質問
15-1. Regex.MatchとRegex.IsMatchはどう使い分ける?
一致するかどうかだけを知りたい場合は、Regex.IsMatchを使用します。
bool exists =Regex.IsMatch(input, @"[0-9]+");一致した文字列、位置、グループなどが必要な場合は、Regex.Matchを使用します。
Match match =Regex.Match(input, @"[0-9]+");if (match.Success){Console.WriteLine(match.Value);}
判定だけの処理でMatchを取得する必要はありません。
15-2. MatchとMatchesの違いは?
Regex.Matchは、最初の一致結果を1つ取得します。
Match match =Regex.Match("A1 B2 C3", @"[0-9]");この場合、最初の1が取得されます。
Regex.Matchesは、すべての一致結果を取得します。
MatchCollection matches =Regex.Matches("A1 B2 C3", @"[0-9]");この場合、1、2、3が取得されます。
15-3. 正規表現で完全一致させるには?
文字列全体を判定する場合は、\Aと\zで囲みます。
string pattern =@"\A[A-Za-z0-9]+\z";Console.WriteLine(Regex.IsMatch("abc123", pattern)); // TrueConsole.WriteLine(Regex.IsMatch("abc-123", pattern)); // False
^と$も利用できますが、Multilineや末尾改行の影響を避けたい入力値判定では、\Aと\zが明確です。
15-4. 正規表現で大文字・小文字を無視するには?
RegexOptions.IgnoreCaseを指定します。
bool result = Regex.IsMatch("Hello",@"hello",RegexOptions.IgnoreCase);機械的な識別子を比較する場合は、CultureInvariantとの組み合わせも検討します。
RegexOptions options =RegexOptions.IgnoreCase |RegexOptions.CultureInvariant;パターン内に(?i)を記述する方法もあります。
(?i)hello15-5. 正規表現で改行を含む文字列を検索するには?
.を改行にも一致させたい場合は、RegexOptions.Singlelineを指定します。
Match match = Regex.Match(input,@"BEGIN.END",RegexOptions.Singleline);パターン内では、(?s)を使用できます。
(?s)BEGIN.ENDオプションを変更せず、任意の文字に一致させる方法もあります。
[\s\S]ただし、意図が分かりやすいSinglelineや、より具体的な文字クラスを優先します。
15-6. 日本語の文字種を判定するには?
ひらがなの簡易判定例です。
\A[\u3041-\u309F]+\z全角カタカナの簡易判定例です。
\A[\u30A1-\u30FF]+\zCJK統合漢字の判定例です。
\A\p{IsCJKUnifiedIdeographs}+\zただし、日本語には半角カタカナ、長音記号、句読点、拡張漢字、結合文字などがあります。利用目的に応じて、許可する文字の範囲を明確にしてください。
15-7. 正規表現が遅いときはどう改善する?
最初に、処理時間を計測し、どのパターンと入力で遅くなるかを確認します。
主な改善方法は次のとおりです。
.を具体的な文字クラスに変更する曖昧な量指定子の入れ子を避ける
選択肢の重複を減らす
アンカーで検索範囲を限定する
入力文字数に上限を設ける
タイムアウトを設定する
同じ
Regexインスタンスを再利用するGeneratedRegexを検討するNonBacktrackingを検討する固定検索なら文字列メソッドへ変更する
バックトラッキングは正規表現の柔軟性を支える一方、性能に大きな影響を与える場合があります。
15-8. メールアドレスやURLを正規表現だけで検証できる?
簡易的な形式チェックはできますが、正規表現だけで完全に検証するのは現実的ではありません。
メールアドレスでは、次のように段階的に確認します。
空文字や長さを確認する
簡易的な形式を確認する
確認メールを送信する
利用者が確認リンクを開いたことを確認する
URLでは、正規表現よりUri.TryCreateを優先します。
bool valid =Uri.TryCreate(input,UriKind.Absolute,out Uri? uri) &&(uri.Scheme == Uri.UriSchemeHttp ||uri.Scheme == Uri.UriSchemeHttps);URLへ実際にアクセスする処理では、形式以外にもSSRF、リダイレクト、プライベートIP、許可スキームなどのセキュリティ対策が必要です。
まとめ
C#では、System.Text.RegularExpressions.Regexクラスを使用して、文字列の判定、検索、抽出、置換、分割を実装できます。
一致するかだけを確認するならRegex.IsMatch、最初の一致結果を取得するならRegex.Match、すべての結果を取得するならRegex.Matchesを使用します。置換にはRegex.Replace、正規表現を区切りとした分割にはRegex.Splitを利用します。
正規表現を使うときは、次の点が特に重要です。
入力値の判定では部分一致と完全一致を区別する
半角数字だけなら
\dではなく[0-9]を検討するC#では逐語的文字列
@"..."を使ってエスケープミスを減らす一致結果を使う前に
Match.Successを確認する日付やURLなどは専用APIと組み合わせる
外部入力を扱う場合はタイムアウトを設定する
曖昧な繰り返しや過剰なバックトラッキングを避ける
固定文字列の処理では通常の文字列メソッドも検討する
複雑なパターンにはコメントと自動テストを用意する
繰り返し使用するパターンでは
GeneratedRegexを検討する
正規表現は、短く書くことよりも、意図どおりに一致し、安全に保守できることが重要です。最初は小さなパターンから作成し、正常系・異常系・境界値をテストしながら段階的に組み立てると、C#の正規表現を実務で安定して活用できるようになります。

