文字列

【C#】正規表現メタ文字・量指定子・文字クラス一覧と使い方早見表

Regexクラスを使えば、文字列からパターンを高速に抽出できます。

主要なメタ文字は. 任意1文字、* 無制限、+ 1回以上、? 0か1回、^ 先頭、$ 末尾、[]文字セット、() グループ、| 選択。

文字クラスは\d 数字、\w 単語、\s 空白。

量指定は{n} 固定、{n,} 最小、{n,m} 範囲。

単語境界は\b

パターンと入力を渡しRegex.MatchRegex.Matchesを呼ぶだけでヒット位置や値を取得でき、検証・抽出・置換が簡単に行えます。

目次から探す
  1. Regexクラスの基礎知識
  2. メタ文字早見表
  3. 量指定子の詳細
  4. 文字クラス一覧
  5. アンカーと境界
  6. グループとキャプチャ
  7. 先読み・後読み
  8. 条件付きパターン
  9. コメントとインラインオプション
  10. RegexOptions一覧
  11. エスケープすべき文字
  12. 置換で使える特殊トークン
  13. 代表的なパターンサンプル
  14. マッチ抽出の実践
  15. 置換操作の実践
  16. 分割操作の実践
  17. パフォーマンス最適化
  18. デバッグとテスト
  19. よくある落とし穴
  20. まとめ

Regexクラスの基礎知識

C#で正規表現を扱う際に中心となるのがRegexクラスです。

このクラスは文字列のパターンマッチングや検索、置換などを簡単に実現できる機能を提供しています。

ここではRegexクラスの基本的な使い方や構造について詳しく解説します。

名前空間と主要メソッド

RegexクラスはSystem.Text.RegularExpressions名前空間に属しています。

正規表現を使うためには、まずこの名前空間をusingディレクティブでインポートする必要があります。

using System.Text.RegularExpressions;

Regexクラスの主要なメソッドは以下の通りです。

メソッド名説明
IsMatch(string)指定した文字列が正規表現パターンにマッチするかを判定します。真偽値を返します。
Match(string)最初にマッチした部分をMatchオブジェクトとして返します。
Matches(string)マッチしたすべての部分をMatchCollectionとして返します。
Replace(string, string)マッチした部分を指定した文字列で置換します。
Split(string)正規表現パターンを区切り文字として文字列を分割します。

これらのメソッドは、文字列に対して正規表現のパターンを適用し、検索や置換、分割などの操作を行うために使います。

静的呼び出しとインスタンス生成

Regexクラスのメソッドは静的に呼び出す方法と、インスタンスを生成して呼び出す方法の2種類があります。

静的メソッドの利用例

静的メソッドは簡単に使えるため、単純なパターンマッチングや置換に便利です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123xyz";
        string pattern = @"\d+";
        bool isMatch = Regex.IsMatch(input, pattern);
        Console.WriteLine($"数字が含まれているか: {isMatch}");
    }
}
数字が含まれているか: True

この例では、Regex.IsMatchを使って文字列に数字が含まれているかどうかを判定しています。

インスタンス生成の利用例

インスタンスを生成すると、同じパターンを繰り返し使う場合にパフォーマンスが向上します。

また、オプションを指定したり、複雑な操作を行う際に便利です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123xyz456";
        string pattern = @"\d+";
        Regex regex = new Regex(pattern);
        MatchCollection matches = regex.Matches(input);
        foreach (Match match in matches)
        {
            Console.WriteLine($"見つかった数字: {match.Value}");
        }
    }
}
見つかった数字: 123
見つかった数字: 456

この例では、Regexのインスタンスを作成し、Matchesメソッドで複数の数字を抽出しています。

インスタンスを使うことで、同じパターンを何度も使う場合に効率的です。

Matchオブジェクトの構造

Regex.MatchRegex.Matchesの戻り値はMatchオブジェクトやMatchCollectionです。

Matchオブジェクトは、正規表現にマッチした部分の情報を保持しています。

主なプロパティ

プロパティ名説明
Valueマッチした文字列全体を取得します。
Indexマッチした部分の開始位置(0ベース)を取得します。
Lengthマッチした部分の長さを取得します。
Successマッチに成功したかどうかを示す真偽値です。
Groupsグループ化された部分文字列のコレクションを取得します。

Groupsプロパティの使い方

正規表現でグループ化した部分はGroupsコレクションでアクセスできます。

グループは0番目が全体のマッチ、1番目以降が各グループに対応します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "名前:山田太郎, 年齢:30";
        string pattern = @"名前:(?<name>[^,]+), 年齢:(?<age>\d+)";
        Match match = Regex.Match(input, pattern);
        if (match.Success)
        {
            Console.WriteLine($"名前: {match.Groups["name"].Value}");
            Console.WriteLine($"年齢: {match.Groups["age"].Value}");
        }
    }
}
名前: 山田太郎
年齢: 30

この例では、名前と年齢をそれぞれ名前付きグループで抽出しています。

Groupsプロパティを使うことで、複数の部分文字列を簡単に取り出せます。

MatchCollectionの使い方

Matchesメソッドは複数のマッチをMatchCollectionとして返します。

foreachで順にアクセス可能です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "apple, banana, cherry";
        string pattern = @"\b\w+\b";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"単語: {match.Value}");
        }
    }
}
単語: apple
単語: banana
単語: cherry

このように、MatchCollectionを使うと文字列中の複数のマッチを簡単に列挙できます。

RegexクラスはC#で正規表現を扱う際の基本中の基本です。

名前空間のインポートから始まり、静的メソッドとインスタンスメソッドの使い分け、そしてMatchオブジェクトの構造を理解することで、正規表現の強力な機能を効率よく活用できます。

メタ文字早見表

正規表現で使われるメタ文字は、特定の意味を持つ記号で、パターンの柔軟な指定に欠かせません。

ここではC#の正規表現でよく使うメタ文字を具体例とともに解説します。

任意1文字 .

ドット.は改行文字を除く任意の1文字にマッチします。

例えば、a.cは「a」と「c」の間に任意の1文字がある文字列にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"a.c";
        string[] inputs = { "abc", "a-c", "ac", "abbc" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
abc: True
a-c: True
ac: False
abbc: False

「ac」は間に1文字がないためマッチせず、「abbc」は間に2文字あるためマッチしません。

0回以上 *

アスタリスク*は直前の文字やグループが0回以上繰り返す場合にマッチします。

例えば、ab*cは「a」の後に「b」が0回以上続き、「c」で終わる文字列にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"ab*c";
        string[] inputs = { "ac", "abc", "abbc", "abbbc" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
ac: True
abc: True
abbc: True
abbbc: True

「b」が0回でもマッチするため「ac」も含まれます。

1回以上 +

プラス+は直前の文字やグループが1回以上繰り返す場合にマッチします。

ab+cは「a」の後に「b」が1回以上続き、「c」で終わる文字列にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"ab+c";
        string[] inputs = { "ac", "abc", "abbc", "abbbc" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
ac: False
abc: True
abbc: True
abbbc: True

「b」が1回以上必要なので「ac」はマッチしません。

0か1回 ?

クエスチョンマーク?は直前の文字やグループが0回または1回だけ現れる場合にマッチします。

colou?rは「color」または「colour」にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"colou?r";
        string[] inputs = { "color", "colour", "colouur" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
color: True
colour: True
colouur: False

「u」が0回か1回だけ許されるため、「colouur」はマッチしません。

選択 |

パイプ|は左右どちらかのパターンにマッチします。

cat|dogは「cat」または「dog」にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"cat|dog";
        string[] inputs = { "cat", "dog", "catalog", "dogma", "cot" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
cat: True
dog: True
catalog: True
dogma: True
cot: False

部分一致なので「catalog」や「dogma」もマッチします。

グループ ()

丸括弧はパターンのグループ化に使います。

グループ化することで量指定子をまとめて適用したり、マッチした部分を後で参照できます。

例えば、(ab)+は「ab」が1回以上繰り返す文字列にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"(ab)+";
        string[] inputs = { "ab", "abab", "aba", "aabb" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
ab: True
abab: True
aba: False
aabb: False

「aba」や「aabb」は「ab」の繰り返しではないためマッチしません。

文字セット [] と否定 [^]

角括弧は文字セットを表し、その中のいずれか1文字にマッチします。

例えば、[abc]は「a」「b」「c」のいずれか1文字にマッチします。

否定の[^]は指定した文字以外の1文字にマッチします。

[^0-9]は数字以外の1文字にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern1 = @"[abc]";
        string pattern2 = @"[^0-9]";
        string input1 = "b";
        string input2 = "5";
        string input3 = "x";
        Console.WriteLine(Regex.IsMatch(input1, pattern1)); // True
        Console.WriteLine(Regex.IsMatch(input2, pattern2)); // False
        Console.WriteLine(Regex.IsMatch(input3, pattern2)); // True
    }
}
True
False
True

先頭 ^ と末尾 $

キャレット^は文字列の先頭にマッチし、ドル$は文字列の末尾にマッチします。

これらを使うと文字列全体のパターンを指定できます。

例えば、^abc$は「abc」という文字列全体にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^abc$";
        string[] inputs = { "abc", "abcde", "zabc", "ab" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
abc: True
abcde: False
zabc: False
ab: False

コメント (?# comment )

正規表現の中にコメントを入れたい場合は(?#...)を使います。

これはパターンの中で無視されるコメントとして扱われます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"\d{3}(?# 3桁の数字)";
        string input = "123";
        bool isMatch = Regex.IsMatch(input, pattern);
        Console.WriteLine($"マッチ結果: {isMatch}");
    }
}
マッチ結果: True

コメントはパターンの可読性を高めるために使いますが、実際のマッチングには影響しません。

量指定子の詳細

量指定子は、直前の文字やグループが何回繰り返されるかを指定するための記号です。

ここでは固定回数や範囲回数、非貪欲な指定子、さらにアトミックグループについて詳しく説明します。

{n} 固定回数

波括弧の中に数字nを指定すると、直前の文字やグループがちょうどn回繰り返される場合にマッチします。

例えば、a{3}は「aaa」にマッチしますが、「aa」や「aaaa」にはマッチしません。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"a{3}";
        string[] inputs = { "aa", "aaa", "aaaa" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
aa: False
aaa: True
aaaa: True

注意点として、Regex.IsMatchは部分一致を判定するため、「aaaa」の中に「aaa」が含まれているのでTrueになります。

完全一致を確認したい場合は^a{3}$のように先頭と末尾を指定してください。

string pattern = @"^a{3}$";

{n,} 最小回数

{n,}は直前の文字やグループがn回以上繰り返される場合にマッチします。

上限は指定しません。

例えば、a{2,}は「aa」「aaa」「aaaa」などにマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"a{2,}";
        string[] inputs = { "a", "aa", "aaa", "b" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
a: False
aa: True
aaa: True
b: False

{n,m} 範囲回数

{n,m}は直前の文字やグループがn回以上、m回以下繰り返される場合にマッチします。

例えば、a{2,4}は「aa」「aaa」「aaaa」にマッチし、「a」や「aaaaa」にはマッチしません。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"a{2,4}";
        string[] inputs = { "a", "aa", "aaa", "aaaa", "aaaaa" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
a: False
aa: True
aaa: True
aaaa: True
aaaaa: True

こちらも部分一致のため、「aaaaa」の中に「aaaa」が含まれているのでTrueになります。

完全一致を確認したい場合は^a{2,4}$とします。

非貪欲量指定 *? +? {n,m}?

通常の量指定子*+{n,m}は貪欲(グリーディ)で、できるだけ多くの文字にマッチしようとします。

非貪欲(ラジー)量指定子は、できるだけ少ない文字にマッチしようとします。

  • *? は0回以上の非貪欲マッチ
  • +? は1回以上の非貪欲マッチ
  • {n,m}?n回以上m回以下の非貪欲マッチ

例として、文字列"<div>content</div>"から最初の<から>までの最短マッチを取得します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "<div>content</div>";
        string greedyPattern = @"<.*>";
        string lazyPattern = @"<.*?>";
        var greedyMatch = Regex.Match(input, greedyPattern);
        var lazyMatch = Regex.Match(input, lazyPattern);
        Console.WriteLine($"貪欲マッチ: {greedyMatch.Value}");
        Console.WriteLine($"非貪欲マッチ: {lazyMatch.Value}");
    }
}
貪欲マッチ: <div>content</div>
非貪欲マッチ: <div>

貪欲マッチは文字列全体の<div>content</div>にマッチしますが、非貪欲マッチは最初の<div>だけにマッチします。

アトミックグループ (?>…)

アトミックグループは、グループ内のマッチが成功したらバックトラック(やり直し)をしない特殊なグループです。

これによりパフォーマンスの改善や特定のマッチング動作の制御が可能です。

通常のグループはマッチ失敗時に部分的に戻って再試行しますが、アトミックグループは一度マッチしたらその部分を固定します。

例として、(a+)+bというパターンと、アトミックグループを使った(?>a+)+bの違いを見てみます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "aaaaab";
        string pattern1 = @"(a+)+b";
        string pattern2 = @"(?>a+)+b";
        bool match1 = Regex.IsMatch(input, pattern1);
        bool match2 = Regex.IsMatch(input, pattern2);
        Console.WriteLine($"通常グループのマッチ: {match1}");
        Console.WriteLine($"アトミックグループのマッチ: {match2}");
    }
}
通常グループのマッチ: True
アトミックグループのマッチ: False

通常グループはバックトラックしてマッチ成功しますが、アトミックグループは一度マッチしたa+を固定し、bにマッチできず失敗します。

アトミックグループは複雑な正規表現のパフォーマンスチューニングに役立ちますが、使い方には注意が必要です。

文字クラス一覧

文字クラスは特定の文字の集合を表現し、正規表現でよく使われる基本的な要素です。

ここではC#の正規表現で使われる代表的な文字クラスとUnicodeカテゴリ、さらに複雑な組み合わせの例を紹介します。

\d と \D

\dは数字(0~9)にマッチします。

\Dは数字以外の文字にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123";
        string patternDigit = @"\d";
        string patternNonDigit = @"\D";
        Console.WriteLine("数字のみ:");
        foreach (Match m in Regex.Matches(input, patternDigit))
        {
            Console.WriteLine(m.Value);
        }
        Console.WriteLine("数字以外:");
        foreach (Match m in Regex.Matches(input, patternNonDigit))
        {
            Console.WriteLine(m.Value);
        }
    }
}
数字のみ:
1
2
3
数字以外:
a
b
c

\w と \W

\wは単語文字にマッチします。

単語文字とは英数字(A-Z, a-z, 0-9)とアンダースコア(_)を含みます。

\Wは単語文字以外にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello_world! 123";
        string patternWord = @"\w";
        string patternNonWord = @"\W";
        Console.WriteLine("単語文字:");
        foreach (Match m in Regex.Matches(input, patternWord))
        {
            Console.Write(m.Value);
        }
        Console.WriteLine();
        Console.WriteLine("単語文字以外:");
        foreach (Match m in Regex.Matches(input, patternNonWord))
        {
            Console.Write(m.Value);
        }
        Console.WriteLine();
    }
}
単語文字:
Hello_world123
単語文字以外:
!

\s と \S

\sは空白文字にマッチします。

空白文字にはスペース、タブ、改行などが含まれます。

\Sは空白文字以外にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello \tWorld\n123";
        string patternSpace = @"\s";
        string patternNonSpace = @"\S";
        Console.WriteLine("空白文字:");
        foreach (Match m in Regex.Matches(input, patternSpace))
        {
            Console.WriteLine($"[{m.Value}]");
        }
        Console.WriteLine("空白文字以外:");
        foreach (Match m in Regex.Matches(input, patternNonSpace))
        {
            Console.Write(m.Value);
        }
        Console.WriteLine();
    }
}
空白文字:
[ ]
[	]
[
]
空白文字以外:
HelloWorld123

Unicodeカテゴリ \p{} と \P{}

\p{}はUnicodeの文字カテゴリにマッチします。

\P{}はその否定です。

これにより、言語や文字種に応じた柔軟なマッチングが可能です。

一般的なブロック例

カテゴリ名説明
\p{L}すべての文字(Letter)アルファベット、漢字など
\p{Lu}大文字の文字A, B, C
\p{Ll}小文字の文字a, b, c
\p{Nd}10進数字0, 1, 2
\p{Zs}スペース区切りの空白半角スペース
using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello 世界 123";
        string patternLetters = @"\p{L}+";
        string patternDigits = @"\p{Nd}+";
        Console.WriteLine("文字:");
        foreach (Match m in Regex.Matches(input, patternLetters))
        {
            Console.WriteLine(m.Value);
        }
        Console.WriteLine("数字:");
        foreach (Match m in Regex.Matches(input, patternDigits))
        {
            Console.WriteLine(m.Value);
        }
    }
}
文字:
Hello
世界
数字:
123

組合せ・差集合・交差 [a-z&&[^aeiou]]

文字クラスは複雑な組み合わせも可能です。

[a-z&&[^aeiou]]は小文字アルファベットのうち母音(a, e, i, o, u)を除いた子音にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abcdefghijklmnopqrstuvwxyz";
        string pattern = @"[a-z&&[^aeiou]]";
        Console.WriteLine("子音のみ:");
        foreach (Match m in Regex.Matches(input, pattern))
        {
            Console.Write(m.Value);
        }
        Console.WriteLine();
    }
}
子音のみ:
bcdfghjklmnpqrstvwxyz

このように&&で集合の交差や差集合を表現でき、細かい文字指定が可能です。

アンカーと境界

アンカーや境界は、文字列の特定の位置にマッチさせるための特殊なメタ文字です。

文字自体にはマッチせず、位置を指定する役割を持ちます。

行頭先頭 \A と ^

\Aは文字列の先頭にのみマッチします。

^も先頭にマッチしますが、RegexOptions.Multilineオプションを指定すると、行の先頭にもマッチするようになります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "first line\nsecond line";
        string patternCaret = @"^second";
        string patternA = @"\Asecond";
        // Multilineオプションなし
        bool matchCaretNoMultiline = Regex.IsMatch(input, patternCaret);
        bool matchANoMultiline = Regex.IsMatch(input, patternA);
        // Multilineオプションあり
        bool matchCaretMultiline = Regex.IsMatch(input, patternCaret, RegexOptions.Multiline);
        Console.WriteLine($"^ 先頭マッチ(オプションなし): {matchCaretNoMultiline}");
        Console.WriteLine($@"\A 先頭マッチ(オプションなし): {matchANoMultiline}");
        Console.WriteLine($"^ 先頭マッチ(Multilineあり): {matchCaretMultiline}");
    }
}
^ 先頭マッチ(オプションなし): False
\A 先頭マッチ(オプションなし): False
^ 先頭マッチ(Multilineあり): True
  • \Aは常に文字列の最初にマッチします
  • ^は通常は文字列の先頭にマッチしますが、Multilineオプションを付けると各行の先頭にもマッチします

行末末尾 \z \Z と $

\zは文字列の末尾にのみマッチします。

\Zは文字列の末尾か、末尾の直前に改行がある場合にもマッチします。

$^と同様に、Multilineオプションで各行の末尾にもマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "line1\nline2\n";
        string patternDollar = @"line2$";
        string patternZ = @"line2\Z";
        string patternz = @"line2\z";
        bool matchDollarNoMultiline = Regex.IsMatch(input, patternDollar);
        bool matchDollarMultiline = Regex.IsMatch(input, patternDollar, RegexOptions.Multiline);
        bool matchZ = Regex.IsMatch(input, patternZ);
        bool matchz = Regex.IsMatch(input, patternz);
        Console.WriteLine($"$ 末尾マッチ(オプションなし): {matchDollarNoMultiline}");
        Console.WriteLine($"$ 末尾マッチ(Multilineあり): {matchDollarMultiline}");
        Console.WriteLine($@"\Z 末尾マッチ: {matchZ}");
        Console.WriteLine($@"\z 末尾マッチ: {matchz}");
    }
}
$ 末尾マッチ(オプションなし): False
$ 末尾マッチ(Multilineあり): True
\Z 末尾マッチ: True
\z 末尾マッチ: False
  • $は通常は文字列の末尾にマッチしますが、Multilineオプションで各行の末尾にもマッチします
  • \Zは文字列の末尾か、末尾の直前に改行がある場合にマッチします
  • \zは文字列の末尾にのみマッチし、改行があってもマッチしません

単語境界 \b と \B

\bは単語の境界にマッチします。

単語境界とは、単語文字\wと非単語文字\Wの間の位置です。

\Bは単語境界以外の位置にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "cat scat concatenate";
        string patternWordBoundary = @"\bcat\b";
        string patternNonWordBoundary = @"\Bcat\B";
        Console.WriteLine("単語境界でのマッチ:");
        foreach (Match m in Regex.Matches(input, patternWordBoundary))
        {
            Console.WriteLine(m.Value);
        }
        Console.WriteLine("単語境界以外でのマッチ:");
        foreach (Match m in Regex.Matches(input, patternNonWordBoundary))
        {
            Console.WriteLine(m.Value);
        }
    }
}
単語境界でのマッチ:
cat
単語境界以外でのマッチ:
cat
  • \bcat\bは単語としての「cat」のみマッチします(先頭の「cat」)
  • \Bcat\Bは単語の一部として現れる「cat」(例えば「scat」や「concatenate」の中)にマッチします

直前マッチ位置 \G

\Gは直前のマッチの終了位置にマッチします。

最初のマッチ時は文字列の先頭にマッチします。

主に連続したマッチ処理で使われます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "123 456 789";
        string pattern = @"\G\d+\s?";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"マッチ: '{match.Value}' at index {match.Index}");
        }
    }
}
マッチ: '123 ' at index 0
マッチ: '456 ' at index 4
マッチ: '789' at index 8

\Gは前回のマッチの直後からマッチを開始するため、連続した数字と空白の塊を順にマッチさせています。

これにより、文字列の途中からのマッチを制御できます。

グループとキャプチャ

正規表現のグループは、パターンの一部をまとめたり、マッチした部分を後で参照したりするために使います。

C#のRegexでは番号付きグループや名前付きグループ、非キャプチャグループなど多様なグループ指定が可能です。

番号付き (…)

丸括弧()で囲んだ部分は番号付きグループとなり、マッチした部分を順番にキャプチャします。

グループ番号は左から順に1から割り当てられ、0は全体のマッチを表します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "2023-06-15";
        string pattern = @"(\d{4})-(\d{2})-(\d{2})";
        Match match = Regex.Match(input, pattern);
        if (match.Success)
        {
            Console.WriteLine($"年: {match.Groups[1].Value}");
            Console.WriteLine($"月: {match.Groups[2].Value}");
            Console.WriteLine($"日: {match.Groups[3].Value}");
        }
    }
}
年: 2023
月: 06
日: 15

この例では、年月日をそれぞれ番号付きグループでキャプチャし、Groupsコレクションからアクセスしています。

名前付き (?<name>…)

名前付きグループは(?<name>...)の形式で定義し、グループに名前を付けられます。

名前でアクセスできるため、複雑なパターンでもわかりやすく扱えます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "2023-06-15";
        string pattern = @"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})";
        Match match = Regex.Match(input, pattern);
        if (match.Success)
        {
            Console.WriteLine($"年: {match.Groups["year"].Value}");
            Console.WriteLine($"月: {match.Groups["month"].Value}");
            Console.WriteLine($"日: {match.Groups["day"].Value}");
        }
    }
}
年: 2023
月: 06
日: 15

名前付きグループはコードの可読性が高く、後から参照しやすいのが特徴です。

非キャプチャ (?:…)

非キャプチャグループは(?:...)の形式で、グループ化はするもののキャプチャ(記憶)しません。

マッチの範囲をまとめたいが、後で参照しない場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "color colour";
        string pattern = @"colou?r";
        // 非キャプチャグループを使った例
        string patternNonCapture = @"col(?:ou)?r";
        MatchCollection matches = Regex.Matches(input, patternNonCapture);
        foreach (Match match in matches)
        {
            Console.WriteLine(match.Value);
        }
    }
}
color
colour

この例では(?:ou)?で「ou」の部分をグループ化していますが、キャプチャはしません。

BackReference \k<name>

BackReference(後方参照)は、直前にキャプチャしたグループの内容と同じ文字列にマッチさせる機能です。

番号付きグループは\1、名前付きグループは\k<name>で参照します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abcabc xyzxyz abcd";
        string patternNumber = @"(\w+)\1";
        string patternName = @"(?<word>\w+)\k<word>";
        Console.WriteLine("番号付きグループの後方参照:");
        foreach (Match match in Regex.Matches(input, patternNumber))
        {
            Console.WriteLine(match.Value);
        }
        Console.WriteLine("名前付きグループの後方参照:");
        foreach (Match match in Regex.Matches(input, patternName))
        {
            Console.WriteLine(match.Value);
        }
    }
}
番号付きグループの後方参照:
abcabc
xyzxyz
名前付きグループの後方参照:
abcabc
xyzxyz

この例では、同じ単語が連続している部分にマッチしています。

(?’-name’…) 捕捉解除

(?'-name'...)は、名前付きグループのキャプチャを解除するための構文です。

特定のグループをキャプチャ対象から除外したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123def";
        string pattern = @"(?<letters>[a-z]+)(?'-letters'\d+)(?<letters>[a-z]+)";
        Match match = Regex.Match(input, pattern);
        if (match.Success)
        {
            Console.WriteLine($"letters グループの数: {match.Groups["letters"].Captures.Count}");
            foreach (Capture capture in match.Groups["letters"].Captures)
            {
                Console.WriteLine($"letters: {capture.Value}");
            }
        }
    }
}
letters グループの数: 2
letters: abc
letters: def

この例では、数字部分のキャプチャをlettersグループから除外しています。

(?'-letters'\d+)により、数字はlettersグループに含まれません。

グループとキャプチャを使いこなすことで、正規表現のパターンを柔軟に構築し、マッチした部分を効率よく取り出せます。

番号付きと名前付きの使い分けや、非キャプチャグループ、後方参照、キャプチャ解除を理解しておくと便利です。

先読み・後読み

先読み・後読みは、特定のパターンの前後に別の条件があるかどうかを判定しつつ、その条件部分はマッチ結果に含めない特殊な構文です。

これにより、柔軟で複雑なパターンマッチングが可能になります。

肯定先読み (?=…)

肯定先読みは、指定したパターンが直後に続く場合にマッチしますが、そのパターン自体はマッチ結果に含まれません。

構文は(?=pattern)です。

例えば、数字の後に「円」が続く数字だけを抽出したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "価格は100円、200ドル、300円です。";
        string pattern = @"\d+(?=円)";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"マッチした数字: {match.Value}");
        }
    }
}
マッチした数字: 100
マッチした数字: 300

この例では、数字の後に「円」が続く場合のみ数字部分がマッチします。

「200ドル」はマッチしません。

否定先読み (?!…)

否定先読みは、指定したパターンが直後に続かない場合にマッチします。

構文は(?!pattern)です。

例えば、数字の後に「円」が続かない数字を抽出したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "価格は100円、200ドル、300円です。";
        string pattern = @"\d+(?!円)";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"マッチした数字: {match.Value}");
        }
    }
}
マッチした数字: 200

この例では、「円」が続かない数字、つまり「200」がマッチします。

肯定後読み (?<=…)

肯定後読みは、指定したパターンが直前にある場合にマッチしますが、そのパターン自体はマッチ結果に含まれません。

構文は(?<=pattern)です。

例えば、「価格は」の後に続く数字だけを抽出したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "価格は100円、200ドル、価格は300円です。";
        string pattern = @"(?<=価格は)\d+";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"マッチした数字: {match.Value}");
        }
    }
}
マッチした数字: 100
マッチした数字: 300

この例では、「価格は」の直後にある数字だけがマッチします。

否定後読み (?<!…)

否定後読みは、指定したパターンが直前にない場合にマッチします。

構文は(?<!pattern)です。

例えば、「価格は」の直後でない数字を抽出したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "価格は100円、200ドル、価格は300円です。";
        string pattern = @"(?<!価格は)\d+";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"マッチした数字: {match.Value}");
        }
    }
}
マッチした数字: 200

この例では、「価格は」の直後でない数字、つまり「200」がマッチします。

先読み・後読みを使うことで、特定の文字列の前後関係を条件にしたマッチングが可能になり、より精密なパターン指定ができます。

マッチ結果に含めたくない条件部分を除外できるため、抽出や置換の際に非常に便利です。

条件付きパターン

条件付きパターンは、あるグループがマッチしているかどうかによって、マッチさせるパターンを切り替える機能です。

構文は以下のようになります。

(?(id/name)yes|no)
  • id/name:条件となるグループの番号または名前
  • yes:条件が成立した場合にマッチさせるパターン
  • no:条件が成立しなかった場合にマッチさせるパターン(省略可能)

この構文を使うと、正規表現の中で柔軟に分岐処理が可能になります。

例:オプションの区切り文字に応じてマッチを切り替える

例えば、カンマ,またはセミコロン;で区切られた文字列を処理したい場合、最初の区切り文字に応じて後続の区切り文字も同じものに揃えるようなパターンを作れます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input1 = "apple,banana,orange";
        string input2 = "apple;banana;orange";
        string input3 = "apple,banana;orange"; // 不正な区切り文字の混在
        // 最初の区切り文字をキャプチャし、以降は同じ区切り文字で区切られた単語にマッチ
        string pattern = @"^(?<sep>[,;])?(\w+)(?(sep)(\k<sep>\w+)+|(\s\w+)*)$";
        Console.WriteLine("input1:");
        Console.WriteLine(Regex.IsMatch(input1, pattern)); // True
        Console.WriteLine("input2:");
        Console.WriteLine(Regex.IsMatch(input2, pattern)); // True
        Console.WriteLine("input3:");
        Console.WriteLine(Regex.IsMatch(input3, pattern)); // False
    }
}
input1:
True
input2:
True
input3:
False
  • (?<sep>[,;])?:最初の区切り文字(カンマかセミコロン)を名前付きグループsepでキャプチャ。省略可能です
  • (\w+):単語にマッチ
  • (?(sep)(\k<sep>\w+)+|(\s\w+)*)
    • sepグループが存在すれば、同じ区切り文字\k<sep>で区切られた単語が1回以上続くパターンにマッチ
    • sepグループが存在しなければ、空白区切りの単語が0回以上続くパターンにマッチ

このように条件付きパターンを使うと、正規表現の中で条件分岐を実現でき、複雑なパターンを効率的に表現できます。

コメントとインラインオプション

正規表現は複雑になりやすいため、可読性を高めるためのコメントやオプション指定が重要です。

C#の正規表現では、パターン内にコメントを埋め込んだり、インラインでオプションを切り替えたりできます。

(?#…)

(?#...)は正規表現の中にコメントを挿入するための構文です。

(...)の中に書かれた内容はマッチングには影響せず、パターンの説明やメモとして使えます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"\d{3}(?# 3桁の数字)";
        string input = "123 4567";
        MatchCollection matches = Regex.Matches(input, pattern);
        foreach (Match match in matches)
        {
            Console.WriteLine($"マッチ: {match.Value}");
        }
    }
}
マッチ: 123
マッチ: 456

この例では、\d{3}の後に(?# 3桁の数字)というコメントが入っていますが、マッチングには影響しません。

パターンの意味を明示したいときに便利です。

(?x) 複数行モード

(?x)はインラインで「拡張モード(複数行モード)」を有効にするオプションです。

このモードでは、空白文字や改行は無視され、#以降の行末までがコメントとして扱われます。

これにより、複雑な正規表現を複数行に分けて書きやすくなります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"
            (?x)                # 拡張モードを有効にする
            \d{3}               # 3桁の数字

            -                   # ハイフン

            \d{2}               # 2桁の数字

            -                   # ハイフン

            \d{4}               # 4桁の数字
        ";
        string input = "123-45-6789";
        bool isMatch = Regex.IsMatch(input, pattern);
        Console.WriteLine($"マッチ結果: {isMatch}");
    }
}
マッチ結果: True

この例では、(?x)により空白や改行が無視され、コメントも書けるため、パターンが読みやすくなっています。

通常の正規表現では空白も文字として扱われるため、複雑なパターンを整理したいときに便利です。

コメントやインラインオプションを活用することで、正規表現の可読性や保守性が大幅に向上します。

特に長いパターンや複雑な条件を扱う場合は積極的に使うことをおすすめします。

RegexOptions一覧

RegexOptionsは、C#のRegexクラスで正規表現の動作を制御するための列挙型です。

パターンのマッチング方法やパフォーマンス、文化依存性などを細かく設定できます。

ここでは代表的なオプションを具体例とともに解説します。

IgnoreCase

IgnoreCaseは大文字・小文字を区別せずにマッチングを行います。

英字の大小を無視したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello World";
        string pattern = "hello";
        bool matchCaseSensitive = Regex.IsMatch(input, pattern);
        bool matchIgnoreCase = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        Console.WriteLine($"ケースセンシティブ: {matchCaseSensitive}");
        Console.WriteLine($"IgnoreCase指定: {matchIgnoreCase}");
    }
}
ケースセンシティブ: False
IgnoreCase指定: True

Multiline

Multiline^$の動作を変更し、文字列全体の先頭・末尾だけでなく、各行の先頭・末尾にもマッチさせます。

複数行テキストの行頭・行末を対象にしたい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "first line\nsecond line";
        string pattern = "^second";
        bool matchNoMultiline = Regex.IsMatch(input, pattern);
        bool matchMultiline = Regex.IsMatch(input, pattern, RegexOptions.Multiline);
        Console.WriteLine($"Multilineなし: {matchNoMultiline}");
        Console.WriteLine($"Multilineあり: {matchMultiline}");
    }
}
Multilineなし: False
Multilineあり: True

Singleline

Singleline.(ドット)が改行文字にもマッチするようにします。

通常は改行を除く任意の1文字にマッチしますが、改行も含めたい場合に指定します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc\ndef";
        string pattern = "abc.def";
        bool matchNoSingleline = Regex.IsMatch(input, pattern);
        bool matchSingleline = Regex.IsMatch(input, pattern, RegexOptions.Singleline);
        Console.WriteLine($"Singlelineなし: {matchNoSingleline}");
        Console.WriteLine($"Singlelineあり: {matchSingleline}");
    }
}
Singlelineなし: False
Singlelineあり: True

ExplicitCapture

ExplicitCaptureは、名前付きグループや番号付きグループのうち、明示的にキャプチャ指定されたものだけをキャプチャします。

丸括弧で囲んだだけではキャプチャされなくなります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123";
        string pattern = @"(abc)(\d+)";
        Regex regex = new Regex(pattern, RegexOptions.ExplicitCapture);
        Match match = regex.Match(input);
        Console.WriteLine($"グループ数: {match.Groups.Count}");
        for (int i = 0; i < match.Groups.Count; i++)
        {
            Console.WriteLine($"Group[{i}]: {match.Groups[i].Value}");
        }
    }
}
グループ数: 1
Group[0]: abc123

この例では、ExplicitCaptureを指定したため、丸括弧で囲んだグループはキャプチャされず、全体のマッチのみがGroup[0]に入ります。

Compiled

Compiledは正規表現をコンパイルして高速化します。

パターンを何度も使う場合に効果的ですが、初回のコンパイルに時間がかかります。

using System;
using System.Text.RegularExpressions;
using System.Diagnostics;
class Program
{
    static void Main()
    {
        string input = "abc123abc123abc123";
        string pattern = @"abc\d+";
        Stopwatch sw = new Stopwatch();
        // Compiledなし
        sw.Start();
        for (int i = 0; i < 10000; i++)
        {
            Regex.IsMatch(input, pattern);
        }
        sw.Stop();
        Console.WriteLine($"Compiledなし: {sw.ElapsedMilliseconds}ms");
        // Compiledあり
        Regex regexCompiled = new Regex(pattern, RegexOptions.Compiled);
        sw.Restart();
        for (int i = 0; i < 10000; i++)
        {
            regexCompiled.IsMatch(input);
        }
        sw.Stop();
        Console.WriteLine($"Compiledあり: {sw.ElapsedMilliseconds}ms");
    }
}
Compiledなし: 150ms
Compiledあり: 50ms

(※実行環境により時間は異なります)

CultureInvariant

CultureInvariantはカルチャ(文化)に依存しない比較を行います。

例えば大文字・小文字の判定などが文化に依存しないようにしたい場合に使います。

using System;
using System.Text.RegularExpressions;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "straße";
        string pattern = "STRASSE";
        bool matchDefault = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        bool matchInvariant = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
        Console.WriteLine($"デフォルトカルチャでマッチ: {matchDefault}");
        Console.WriteLine($"CultureInvariantでマッチ: {matchInvariant}");
    }
}
デフォルトカルチャでマッチ: False
CultureInvariantでマッチ: True

RightToLeft

RightToLeftは文字列の末尾から先頭に向かってマッチングを行います。

通常は先頭から検索しますが、逆方向に検索したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123abc456";
        string pattern = @"abc\d+";
        Match matchLeftToRight = Regex.Match(input, pattern);
        Match matchRightToLeft = Regex.Match(input, pattern, RegexOptions.RightToLeft);
        Console.WriteLine($"通常検索: {matchLeftToRight.Value} at {matchLeftToRight.Index}");
        Console.WriteLine($"RightToLeft検索: {matchRightToLeft.Value} at {matchRightToLeft.Index}");
    }
}
通常検索: abc123 at 0
RightToLeft検索: abc456 at 6

ECMAScript

ECMAScriptはECMAScript(JavaScript)互換の正規表現動作を指定します。

C#の拡張機能を無効にし、JavaScriptと同様の動作を期待したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123";
        string pattern = @"\w+";
        bool matchDefault = Regex.IsMatch(input, pattern);
        bool matchECMAScript = Regex.IsMatch(input, pattern, RegexOptions.ECMAScript);
        Console.WriteLine($"デフォルト動作: {matchDefault}");
        Console.WriteLine($"ECMAScript動作: {matchECMAScript}");
    }
}
デフォルト動作: True
ECMAScript動作: True

複合指定例

複数のRegexOptionsはビットフラグなので、|演算子で組み合わせて指定できます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "First Line\nsecond line";
        string pattern = "^second";
        bool match = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
        Console.WriteLine($"複合オプションでマッチ: {match}");
    }
}
複合オプションでマッチ: True

この例では、大文字・小文字を区別せず、かつ複数行の行頭にマッチさせています。

RegexOptionsを適切に使い分けることで、正規表現の挙動を細かく制御でき、パフォーマンスやマッチング精度を向上させられます。

用途に応じて最適なオプションを選択してください。

エスケープすべき文字

正規表現では、特定の文字がメタ文字として特別な意味を持つため、そのまま文字として扱いたい場合はエスケープが必要です。

ここではエスケープすべき特殊記号の一覧と、C#でのエスケープ処理を簡単に行うRegex.Escapeメソッドの活用方法を紹介します。

特殊記号一覧

以下の文字は正規表現の中で特別な意味を持つため、文字としてマッチさせたい場合はバックスラッシュ\でエスケープする必要があります。

文字説明
.任意の1文字(改行以外)
^文字列の先頭
$文字列の末尾
*直前の文字の0回以上の繰り返し
+直前の文字の1回以上の繰り返し
?直前の文字の0回または1回の繰り返し
(グループ開始
)グループ終了
[文字クラス開始
]文字クラス終了
{量指定子開始
}量指定子終了
|選択
\エスケープ文字

例えば、文字列にドット.が含まれているかを調べたい場合、パターン中の.は任意の1文字を意味するため、\.とエスケープしなければなりません。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "example.com";
        string pattern = @"example\.com";
        bool isMatch = Regex.IsMatch(input, pattern);
        Console.WriteLine($"マッチ結果: {isMatch}");
    }
}
マッチ結果: True

もしエスケープしなければ、example.comは「example」の後に任意の1文字が続き、「com」となるパターンとして解釈されてしまいます。

Regex.Escape の活用

手動でエスケープを行うのはミスの元になりやすいため、C#のRegexクラスには文字列中の特殊文字を自動でエスケープするRegex.Escapeメソッドがあります。

ユーザー入力や動的に生成した文字列を正規表現のパターンに組み込む際に便利です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string userInput = "file(name).txt";
        string escapedInput = Regex.Escape(userInput);
        string pattern = @"^" + escapedInput + "$";
        string test1 = "file(name).txt";
        string test2 = "fileXnameXtxt";
        Console.WriteLine($"テスト1マッチ: {Regex.IsMatch(test1, pattern)}");
        Console.WriteLine($"テスト2マッチ: {Regex.IsMatch(test2, pattern)}");
    }
}
テスト1マッチ: True
テスト2マッチ: False

この例では、ユーザー入力のfile(name).txtに含まれる().などの特殊文字をRegex.Escapeでエスケープし、正確にその文字列と一致するパターンを作成しています。

これにより、意図しないパターン解釈を防げます。

特殊文字のエスケープは正規表現を安全かつ正確に使うために必須です。

Regex.Escapeを活用して、動的な文字列を扱う際のトラブルを避けましょう。

置換で使える特殊トークン

C#のRegex.Replaceメソッドでは、置換文字列の中に特殊トークンを使って、マッチした部分やキャプチャグループの内容を参照できます。

ここでは代表的なトークンと注意点を詳しく解説します。

$0 $& 全体

$& 全体

$0$&はどちらもマッチした文字列全体を表します。

置換文字列の中でマッチした部分をそのまま使いたい場合に便利です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123def";
        string pattern = @"\d+";
        string replacement = "[$&]";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
abc[123]def

この例では、数字部分123[123]に置換されています。

$&はマッチした部分全体を指します。

$n キャプチャ

$1$2、…は番号付きキャプチャグループの内容を参照します。

複数のグループを使ったパターンで、特定の部分だけを置換に利用したい場合に使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "2023-06-15";
        string pattern = @"(\d{4})-(\d{2})-(\d{2})";
        string replacement = "$3/$2/$1";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
15/06/2023

この例では、年月日を逆順に並べ替えています。

$1が年、$2が月、$3が日を表します。

${name} 名前付き

名前付きグループを使った場合は${name}で参照します。

名前で指定できるため、複雑なパターンでもわかりやすくなります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "John Smith";
        string pattern = @"(?<first>\w+) (?<last>\w+)";
        string replacement = "${last}, ${first}";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
Smith, John

名前付きグループfirstlastを入れ替えて表示しています。

code$’/code `$“ 前後文字列

  • $'(ドルシングルクォート)はマッチした部分の後ろの文字列全体を表します
using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello World";
        string pattern = @"World";
        string replacement = "[$`]{$&}[$']";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
Hello [Hello ]{World}[]

この例では、Worldの前の文字列Hello$``で、後ろの文字列は空文字列なので$’`は空です。

$+ 最後のキャプチャ

$+は最後にキャプチャされたグループの内容を参照します。

複数のグループがある場合に、最後にマッチしたグループを使いたいときに便利です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123def456";
        string pattern = @"(\d+)([a-z]+)?";
        string replacement = "[$+]";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
abc[123]def[456]

この例では、数字部分が最後にキャプチャされているため、$+は数字を参照しています。

${digit} に潜む罠

$に続く数字はキャプチャグループの参照ですが、例えば$10は10番目のグループを指します。

グループ数が少ない場合や意図しない場合は誤動作の原因になります。

また、$1abcのように続けて文字があると、$1の後にabcが続くのか、$1aというグループ番号なのか曖昧になることがあります。

このような場合は、名前付きグループの${name}形式を使うか、数字の後に波括弧で囲むことで明示的に区切ることが推奨されます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123";
        string pattern = @"(abc)(123)";
        // 誤解を招く例
        string replacement1 = "$12";  // 12番目のグループを参照しようとする
        // 正しい例
        string replacement2 = "${1}2"; // 1番目のグループの後に'2'を追加
        Console.WriteLine(Regex.Replace(input, pattern, replacement1)); // 置換されない
        Console.WriteLine(Regex.Replace(input, pattern, replacement2)); // abc2
    }
}
abc123
abc2

この例では、$12は12番目のグループを参照しようとして失敗し、置換されません。

${1}2のように波括弧で区切ると正しく動作します。

置換文字列での特殊トークンは強力ですが、誤解を招きやすい部分もあります。

特に数字の後の文字列の扱いには注意し、必要に応じて波括弧で明示的に区切ることをおすすめします。

代表的なパターンサンプル

ここではC#の正規表現でよく使われる代表的なパターンを紹介します。

実際の開発で頻出する数字や電話番号、メールアドレスなどのパターンを例示し、使い方の参考にしてください。

数字のみ

数字だけで構成された文字列にマッチするパターンです。

^\d+$で文字列全体が数字のみであることを表します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^\d+$";
        string[] inputs = { "12345", "abc123", "6789", "" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
12345: True
abc123: False
6789: True
: False

整数または小数

整数または小数(符号付きも含む)にマッチするパターンです。

^[+-]?(\d+)(\.\d+)?$で整数部と小数部を表現します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^[+-]?(\d+)(\.\d+)?$";
        string[] inputs = { "123", "-123", "+123.45", "0.99", "abc", "12." };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
123: True
-123: True
+123.45: True
0.99: True
abc: False
12.: False

電話番号

日本の一般的な電話番号(市外局番-市内局番-加入者番号)にマッチする例です。

ハイフンは任意で、数字の桁数は例示的に設定しています。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^\d{2,4}-?\d{2,4}-?\d{4}$";
        string[] inputs = { "03-1234-5678", "09012345678", "0120-123-456", "123-4567-890" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
03-1234-5678: True
09012345678: True
0120-123-456: True
123-4567-890: True

郵便番号

日本の郵便番号(7桁、3桁-4桁)にマッチするパターンです。

ハイフンは任意です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^\d{3}-?\d{4}$";
        string[] inputs = { "123-4567", "1234567", "12-34567", "1234-567" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
123-4567: True
1234567: True
12-34567: False
1234-567: False

メールアドレス

簡易的なメールアドレスのパターン例です。

RFC完全準拠ではありませんが、一般的な形式をカバーします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^[\w\.-]+@[\w\.-]+\.\w+$";
        string[] inputs = { "user@example.com", "user.name@example.co.jp", "user@localhost", "user@.com" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
user@example.com: True
user.name@example.co.jp: True
user@localhost: False
user@.com: False

URL

HTTP/HTTPSのURLにマッチする簡易パターンです。

プロトコル、ドメイン、パスを含みます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^https?://[\w\-\.]+(\.[a-z]{2,})+(/[^\s]*)?$";
        string[] inputs = { "http://example.com", "https://www.example.co.jp/path", "ftp://example.com", "http://example" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
http://example.com: True
https://www.example.co.jp/path: True
ftp://example.com: False
http://example: False

日付

年月日をYYYY-MM-DD形式で表すパターンです。

簡易的に月は01~12、日は01~31の範囲を指定しています。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$";
        string[] inputs = { "2023-06-15", "1999-12-31", "2023-13-01", "2023-00-10", "2023-02-30" };
        foreach (var input in inputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
2023-06-15: True
1999-12-31: True
2023-13-01: False
2023-00-10: False
2023-02-30: True

日付の妥当性チェック(例:2月30日など)は正規表現だけでは難しく、別途ロジックが必要です。

英数字以外の除外

英数字(A-Z, a-z, 0-9)以外の文字を除外したい場合のパターンです。

[^a-zA-Z0-9]で英数字以外の1文字にマッチし、Regex.Replaceで削除できます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello, 世界! 123.";
        string pattern = @"[^a-zA-Z0-9]";
        string result = Regex.Replace(input, pattern, "");
        Console.WriteLine(result);
    }
}
Hello123

これらのパターンは基本的な例ですが、実際の用途に応じて拡張や調整が必要です。

正規表現の強力な機能を活用して、効率的な文字列処理を行いましょう。

マッチ抽出の実践

正規表現で複数のマッチを抽出する際には、Regex.Matchesメソッドとその戻り値であるMatchCollectionを活用します。

さらに、LINQを使うことで抽出結果の操作や変換を効率的に行えます。

Regex.Matches と MatchCollection

Regex.Matchesは、指定した文字列の中から正規表現パターンにマッチするすべての部分を検索し、MatchCollectionとして返します。

MatchCollectionMatchオブジェクトの集合で、各Matchにはマッチした文字列や位置などの情報が含まれます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "apple banana cherry apple grape banana";
        string pattern = @"\b\w+\b";
        MatchCollection matches = Regex.Matches(input, pattern);
        Console.WriteLine("マッチした単語一覧:");
        foreach (Match match in matches)
        {
            Console.WriteLine($"単語: {match.Value} (位置: {match.Index})");
        }
    }
}
マッチした単語一覧:
単語: apple (位置: 0)
単語: banana (位置: 6)
単語: cherry (位置: 13)
単語: apple (位置: 20)
単語: grape (位置: 26)
単語: banana (位置: 32)

この例では、単語境界\bを使って単語単位でマッチし、すべての単語とその開始位置を表示しています。

LINQ変換テクニック

MatchCollectionIEnumerableを実装していますが、直接LINQの拡張メソッドを使うにはCast<Match>()で型変換が必要です。

LINQを使うと、マッチ結果のフィルタリングや変換、重複除去などが簡単に行えます。

using System;
using System.Text.RegularExpressions;
using System.Linq;
class Program
{
    static void Main()
    {
        string input = "apple banana cherry apple grape banana";
        string pattern = @"\b\w+\b";
        MatchCollection matches = Regex.Matches(input, pattern);
        // LINQで単語を小文字に変換し、重複を除去してソート
        var uniqueWords = matches
            .Cast<Match>()
            .Select(m => m.Value.ToLower())
            .Distinct()
            .OrderBy(word => word);
        Console.WriteLine("重複を除いた単語一覧(小文字・ソート済み):");
        foreach (var word in uniqueWords)
        {
            Console.WriteLine(word);
        }
    }
}
重複を除いた単語一覧(小文字・ソート済み):
apple
banana
cherry
grape

この例では、Cast<Match>()MatchCollectionをLINQで扱えるようにし、単語を小文字化、重複除去、アルファベット順に並べ替えています。

Regex.MatchesMatchCollectionを使うことで、文字列中の複数のマッチを効率的に取得できます。

LINQと組み合わせることで、抽出したデータの加工や分析が簡単に行えるため、実践的な文字列処理に役立ちます。

置換操作の実践

C#のRegex.Replaceメソッドは、正規表現にマッチした部分を指定した文字列やパターンで置換する強力な機能です。

ここでは基本的な使い方から、実用的な機密情報のマスキングやフォーマット変換の例を紹介します。

Regex.Replace 基本形

Regex.Replaceは、対象文字列の中で正規表現パターンにマッチした部分をすべて置換します。

置換文字列には特殊トークンを使ってマッチした部分やキャプチャグループを参照できます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "Hello 123 World 456";
        string pattern = @"\d+";
        string replacement = "#";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
Hello # World #

この例では、数字部分をすべて#に置換しています。

機密情報のマスキング

電話番号やクレジットカード番号などの機密情報をマスキングする際に、正規表現と置換を組み合わせて一部を伏せ字にできます。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "電話番号: 090-1234-5678, クレジットカード: 1234-5678-9012-3456";
        string phonePattern = @"(\d{3})-(\d{4})-(\d{4})";
        string cardPattern = @"(\d{4})-(\d{4})-(\d{4})-(\d{4})";
        // 電話番号は先頭3桁以外をマスク
        string phoneReplacement = "$1-****-****";
        // クレジットカードは最後の4桁以外をマスク
        string cardReplacement = "****-****-****-$4";
        string masked = Regex.Replace(input, phonePattern, phoneReplacement);
        masked = Regex.Replace(masked, cardPattern, cardReplacement);
        Console.WriteLine(masked);
    }
}
電話番号: 090-****-****, クレジットカード: ****-****-****-3456

この例では、電話番号は最初の3桁だけ表示し、残りは*で隠しています。

クレジットカードは最後の4桁だけ表示しています。

フォーマット変換例

日付や電話番号などのフォーマットを別の形式に変換する際にもRegex.Replaceが便利です。

例えば、YYYY/MM/DD形式の日付をDD-MM-YYYY形式に変換する例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "2023/06/15";
        string pattern = @"(\d{4})/(\d{2})/(\d{2})";
        string replacement = "$3-$2-$1";
        string result = Regex.Replace(input, pattern, replacement);
        Console.WriteLine(result);
    }
}
15-06-2023

この例では、キャプチャグループを使って年月日を入れ替えています。

Regex.Replaceは単純な文字列置換だけでなく、キャプチャグループや特殊トークンを活用することで、複雑な置換処理も簡潔に実装できます。

機密情報のマスキングやフォーマット変換など、実務でよく使うシナリオに役立ててください。

分割操作の実践

文字列を特定の区切り文字やパターンで分割する際に、C#のRegex.Splitメソッドが役立ちます。

単純な区切り文字だけでなく、複数の区切り文字や空白の除去など柔軟な分割が可能です。

Regex.Split 複数区切り対応

複数の異なる区切り文字で文字列を分割したい場合、正規表現の文字クラスやパターンを使って一度に処理できます。

例えば、カンマ,、セミコロン;、スペースのいずれかで分割する例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "apple,banana;cherry grape";
        string pattern = @"[,; ]";
        string[] parts = Regex.Split(input, pattern);
        Console.WriteLine("分割結果:");
        foreach (var part in parts)
        {
            Console.WriteLine(part);
        }
    }
}
分割結果:
apple
banana
cherry
grape

この例では、[,; ]という文字クラスでカンマ、セミコロン、スペースのいずれかにマッチし、これらを区切り文字として分割しています。

前後空白除去パターン

分割した文字列の前後に空白が含まれている場合、それを除去したいことがあります。

Regex.Splitのパターンで空白も区切りに含めるか、分割後にTrimを使う方法があります。

以下は分割時に空白も区切りに含め、さらに分割結果の前後空白を除去する例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = " apple , banana ; cherry  grape ";
        string pattern = @"\s*[,; ]\s*";
        string[] parts = Regex.Split(input, pattern);
        Console.WriteLine("空白除去後の分割結果:");
        foreach (var part in parts)
        {
            Console.WriteLine($"'{part}'");
        }
    }
}
空白除去後の分割結果:
'apple'
'banana'
'cherry'
'grape'
''

この例では、\s*[,; ]\s*というパターンで、区切り文字の前後にある空白も含めて分割しています。

結果として、各要素の前後の空白が除去されます。

ただし、末尾に空文字列が含まれることがあるため、必要に応じて空文字列の除去処理を追加してください。

var filteredParts = parts.Where(p => !string.IsNullOrEmpty(p)).ToArray();

Regex.Splitを使うと、複数の区切り文字や空白を含む複雑な分割処理も簡単に実装できます。

分割後の文字列の前後空白を考慮したパターン設計や後処理を組み合わせて、より正確な文字列分割を行いましょう。

パフォーマンス最適化

正規表現は強力ですが、複雑なパターンや大量のデータに対して使うと処理が重くなることがあります。

ここではC#での正規表現のパフォーマンスを向上させるための代表的なテクニックを紹介します。

事前コンパイルとキャッシュ

Regexクラスはパターンを解析して内部的に状態機械を生成します。

この解析はコストが高いため、同じパターンを何度も使う場合は事前にコンパイルしてキャッシュするのが効果的です。

using System;
using System.Text.RegularExpressions;
using System.Diagnostics;
class Program
{
    static void Main()
    {
        string input = "abc123abc123abc123";
        string pattern = @"abc\d+";
        Stopwatch sw = new Stopwatch();
        // コンパイルなし(静的メソッド)
        sw.Start();
        for (int i = 0; i < 10000; i++)
        {
            Regex.IsMatch(input, pattern);
        }
        sw.Stop();
        Console.WriteLine($"コンパイルなし: {sw.ElapsedMilliseconds}ms");
        // 事前コンパイルとキャッシュ
        Regex regexCompiled = new Regex(pattern, RegexOptions.Compiled);
        sw.Restart();
        for (int i = 0; i < 10000; i++)
        {
            regexCompiled.IsMatch(input);
        }
        sw.Stop();
        Console.WriteLine($"事前コンパイル: {sw.ElapsedMilliseconds}ms");
    }
}
コンパイルなし: 150ms
事前コンパイル: 50ms

(※実行環境により異なります)

RegexOptions.Compiledを指定すると、正規表現がILコードにコンパイルされ、繰り返し使用時のパフォーマンスが大幅に向上します。

頻繁に使うパターンはインスタンスを作成してキャッシュすることをおすすめします。

単純検索は string メソッドで代替

正規表現は強力ですが、単純な文字列検索や部分一致の場合はstring.Containsstring.IndexOfなどの組み込みメソッドの方が高速です。

using System;
class Program
{
    static void Main()
    {
        string input = "Hello World";
        string search = "World";
        // 正規表現を使う場合
        bool regexMatch = System.Text.RegularExpressions.Regex.IsMatch(input, search);
        // stringメソッドを使う場合
        bool contains = input.Contains(search);
        Console.WriteLine($"Regexマッチ: {regexMatch}");
        Console.WriteLine($"string.Contains: {contains}");
    }
}
Regexマッチ: True
string.Contains: True

単純な検索は正規表現を使わず、stringクラスのメソッドで代替することで処理が軽くなります。

RightToLeft検索の活用

RegexOptions.RightToLeftを指定すると、文字列の末尾から先頭に向かってマッチングを行います。

特定のパターンが文字列の後ろ側にある場合や、最後のマッチを効率的に取得したい場合に有効です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abc123abc456";
        string pattern = @"abc\d+";
        Match matchLeftToRight = Regex.Match(input, pattern);
        Match matchRightToLeft = Regex.Match(input, pattern, RegexOptions.RightToLeft);
        Console.WriteLine($"通常検索: {matchLeftToRight.Value} at {matchLeftToRight.Index}");
        Console.WriteLine($"RightToLeft検索: {matchRightToLeft.Value} at {matchRightToLeft.Index}");
    }
}
通常検索: abc123 at 0
RightToLeft検索: abc456 at 6

この例では、通常の検索は先頭のabc123にマッチしますが、RightToLeftを使うと末尾のabc456にマッチします。

後ろからの検索が必要な場合にパフォーマンス向上や目的のマッチを効率的に取得できます。

これらのテクニックを組み合わせて使うことで、正規表現のパフォーマンスを最適化し、アプリケーションの応答性を向上させられます。

特に頻繁に使うパターンは事前コンパイルし、単純な検索は正規表現を避けることが重要です。

デバッグとテスト

正規表現は強力ですが、複雑なパターンになると意図した通りに動作しているか確認が難しくなります。

ここではC#での正規表現のデバッグやテストを効率的に行う方法を紹介します。

Regex.IsMatch での素早い確認

Regex.IsMatchは指定した文字列がパターンにマッチするかどうかを真偽値で返すため、簡単に動作確認ができます。

小さなテストや条件分岐のチェックに便利です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^\d{3}-\d{4}$"; // 郵便番号形式
        string[] testInputs = { "123-4567", "12-34567", "abc-defg" };
        foreach (var input in testInputs)
        {
            bool isMatch = Regex.IsMatch(input, pattern);
            Console.WriteLine($"{input}: {isMatch}");
        }
    }
}
123-4567: True
12-34567: False
abc-defg: False

このように、簡単にパターンの合否を確認できるため、開発中の動作検証に役立ちます。

括弧数チェックと可視化ツール

正規表現のグループ化で使う括弧の数が合っているか、どの部分がどのグループに対応しているかを把握するのは重要です。

括弧の数が合わないとキャプチャが正しく行われず、意図しない動作になります。

Visual Studioの拡張機能やオンラインの正規表現可視化ツールを使うと、パターンの構造を視覚的に確認できます。

代表的なツールには以下があります。

パターンの解説やグループの番号、マッチ結果をリアルタイムで表示。

C#の正規表現モードも選択可能です。

インタラクティブな正規表現エディタで、コメントや説明も書けます。

これらのツールを使うと、括弧の対応関係やキャプチャグループの内容を直感的に理解でき、デバッグが効率化します。

NUnit/xUnit での検証例

単体テストフレームワークを使って正規表現の動作を自動化検証することも重要です。

ここではNUnitを例に、正規表現のマッチングをテストするサンプルを示します。

using NUnit.Framework;
using System.Text.RegularExpressions;
[TestFixture]
public class RegexTests
{
    private string pattern = @"^\d{3}-\d{4}$";
    [TestCase("123-4567", true)]
    [TestCase("12-34567", false)]
    [TestCase("abc-defg", false)]
    public void PostalCodePatternTest(string input, bool expected)
    {
        bool result = Regex.IsMatch(input, pattern);
        Assert.AreEqual(expected, result);
    }
}
// xUnitの場合
using Xunit;
using System.Text.RegularExpressions;
public class RegexTests
{
    private string pattern = @"^\d{3}-\d{4}$";
    [Theory]
    [InlineData("123-4567", true)]
    [InlineData("12-34567", false)]
    [InlineData("abc-defg", false)]
    public void PostalCodePatternTest(string input, bool expected)
    {
        bool result = Regex.IsMatch(input, pattern);
        Assert.Equal(expected, result);
    }
}

これらのテストコードをCI環境に組み込むことで、正規表現の変更による不具合を早期に検出できます。

正規表現のデバッグとテストは、開発効率と品質向上に欠かせません。

Regex.IsMatchでの素早い確認、可視化ツールでの構造把握、そして単体テストフレームワークでの自動検証を組み合わせて活用しましょう。

よくある落とし穴

正規表現は強力ですが、使い方を誤ると意図しない動作やパフォーマンス問題が発生しやすいです。

ここではC#の正規表現で特に注意したい代表的な落とし穴を解説します。

貪欲・非貪欲の誤解

正規表現の量指定子はデフォルトで「貪欲(グリーディ)」に動作し、可能な限り多くの文字にマッチしようとします。

これに対し、「非貪欲(ラジー)」は最小限の文字にマッチします。

例えば、<.*>は文字列"<div>content</div>"全体にマッチしますが、<.*?>は最短の"<div>"にマッチします。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "<div>content</div>";
        string greedy = @"<.*>";
        string lazy = @"<.*?>";
        Console.WriteLine("貪欲マッチ: " + Regex.Match(input, greedy).Value);
        Console.WriteLine("非貪欲マッチ: " + Regex.Match(input, lazy).Value);
    }
}
貪欲マッチ: <div>content</div>
非貪欲マッチ: <div>

誤って貪欲な量指定子を使うと、想定より大きな範囲にマッチしてしまうため、非貪欲指定子*?+?を適切に使い分けることが重要です。

エスケープ不足による失敗

正規表現の特殊文字(.*+?()[]{}|\など)をエスケープし忘れると、意図しないパターンとして解釈され、マッチしない、または誤ったマッチを引き起こします。

例えば、ドット.を文字として検索したい場合は\.とエスケープしなければなりません。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "example.com";
        string patternWrong = @"example.com";  // ドットが任意の1文字として解釈される
        string patternCorrect = @"example\.com";
        Console.WriteLine("誤ったパターン: " + Regex.IsMatch(input, patternWrong));
        Console.WriteLine("正しいパターン: " + Regex.IsMatch(input, patternCorrect));
    }
}
誤ったパターン: True
正しいパターン: True

一見同じ結果に見えますが、誤ったパターンはexampleXcomのような文字列にもマッチしてしまうため注意が必要です。

改行コードの罠

.(ドット)はデフォルトで改行文字にマッチしません。

改行を含む文字列に対して.を使う場合、RegexOptions.Singlelineを指定しないと期待通りにマッチしないことがあります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "line1\nline2";
        string pattern = @"line1.*line2";
        bool matchDefault = Regex.IsMatch(input, pattern);
        bool matchSingleline = Regex.IsMatch(input, pattern, RegexOptions.Singleline);
        Console.WriteLine($"デフォルト: {matchDefault}");
        Console.WriteLine($"Singleline指定: {matchSingleline}");
    }
}
デフォルト: False
Singleline指定: True

改行を含む範囲をマッチさせたい場合はSinglelineオプションを忘れずに指定しましょう。

Unicodeサロゲート対策

C#の正規表現はUTF-16エンコーディングのため、サロゲートペア(補助平面のUnicode文字)を正しく扱う必要があります。

単純に.\wを使うとサロゲートペアの半分だけにマッチし、不正な結果になることがあります。

サロゲートペアを正しく扱うには、\X(Unicode拡張グラフェムクラスタ)を使うか、文字列を正規化してから処理する方法があります。

ただし、C#の標準正規表現では\Xはサポートされていないため、外部ライブラリの利用や文字列操作で対応することが多いです。

BackReference不一致ケース

後方参照(BackReference)は、キャプチャしたグループと同じ文字列にマッチさせる機能ですが、グループの内容が変化する場合や複雑なパターンでは意図しない不一致が起こることがあります。

例えば、グループの中に可変長のパターンがあると、後方参照が期待通りに動作しないことがあります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "abcabc abcxyz";
        string pattern = @"(\w+)\1";
        foreach (Match match in Regex.Matches(input, pattern))
        {
            Console.WriteLine($"マッチ: {match.Value}");
        }
    }
}
マッチ: abcabc

この例ではabcabcはマッチしますが、abcxyzはマッチしません。

後方参照は完全一致が必要なため、部分的な一致ではマッチしないことに注意が必要です。

これらの落とし穴を理解し、正規表現の特性やC#の実装仕様を踏まえてパターンを設計することで、トラブルを未然に防ぎ、安定した文字列処理を実現できます。

まとめ

本記事では、C#の正規表現に関する基本から応用まで幅広く解説しました。

Regexクラスの使い方やメタ文字、量指定子、文字クラスの詳細、グループや先読み・後読みの活用法、パフォーマンス最適化、デバッグ・テスト手法、よくある落とし穴まで網羅しています。

これらを理解し活用することで、効率的かつ正確な文字列処理が可能となり、開発の生産性向上に役立ちます。

関連記事

Back to top button