正規表現(regular expressions, regexp, regex)とは、複数のファイル指定などで用いられるワイルドカード文字などのように、文字列の中から特定のパターンに一致する部分を検索する(パターンマッチングを行う)ためのものです。 正規表現を用いることで様々なパターンに該当する文字列の検索・置換・削除・抽出などを行うことができます。

正規表現という名称からは難しい概念のように見えますが、実際のところはワイルドカードと同じように一連のルールが定められたパターンを表現する一種の言語です。 正規表現では、「httpで始まる文字列」や「途中に3桁以上の数値が含まれる文字列」というようなパターンを記号的に表記します。 例えば"*.txt"というワイルドカードでは「.txtで終わる(=拡張子がtxtである)全てのファイル」を表しますが、正規表現ではこれを"\.txt$"と表現することができます。

.NET Frameworkでの文字列クラスであるString型にはIndexOf, StartsWith, Contains, Equalsなどの文字列検索・比較のメソッドが用意されています。 こういったメソッドだけでも文字列に対する複雑なパターンマッチングを行うことはできますが、パターンが複雑になればそれに比例してコードも複雑になるデメリットがあります。 一方、正規表現を用いれば複雑なパターンマッチングでも比較的シンプルなコードで記述することができます。 またOR条件での検索や文字種を限定した抽出など、Stringクラスのメソッドでは用意されていないようなパターンでのマッチングを行うこともできます。 (.NET Frameworkで使用できる正規表現)

.NET Frameworkでの正規表現によるパターンマッチングは、Regexクラス(System.Text.RegularExpressions名前空間)を使って行います。 Stringクラスの各種メソッドでは正規表現によるパターンマッチングを行うことはできません。 また、PerlやJavaScript等で用いることができる/\.txt$/のような正規表現リテラルは、C#やVB.NETには存在しないため用いることはできません。


§1 概略

.NET Frameworkでは、System.Text.RegularExpressions名前空間にあるクラス群を使うことで正規表現に関する機能を使うことが出来ます。 このうちのRegexクラスが、正規表現を使ったパターンマッチングと文字列操作を行うためのクラスです。

§1.1 Regexクラスを用いたパターンマッチング

以下はRegexクラスを用いてごく基本的なパターンマッチングの行う例です。 Regex.IsMatchメソッドは、指定した文字列が正規表現にマッチするかどうかをTrue/Falseで返します。

Regexクラスを用いて文字列が正規表現にマッチするか調べる
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 「".txt"で終わるかどうか」を表す正規表現
    var pattern = @"\.txt$";

    // 文字列が正規表現にマッチするかどうか調べる
    Console.WriteLine(Regex.IsMatch("sample.txt", pattern));

    Console.WriteLine(Regex.IsMatch("sample.dat", pattern));
  }
}
実行結果
True
False

C#では、文字列中の"\"は後続の文字に対するエスケープを表します。 正規表現でも"\"が後続の正規表現要素に対するエスケープを表します。

上記の例における\.は、正規表現要素である.が単なるピリオドとして解釈されるよう、\を前置することでエスケープしています。 さらに、\C#文字列におけるエスケープ記号として解釈されないよう(記述されたまま解釈されるよう)にするため、文字列に@を前置して逐語的文字列リテラルとして記述しています。

逐語的文字列リテラルを用いない場合、上記の正規表現は"\\.txt$"と記述する必要があります。

この例のようにC#で正規表現を記述する場合、逐語的文字列リテラルを使用することが多くなります。 これはディレクトリ区切り記号"\"を含むファイルパスを記述する際にもよく用いられます。

逐語的文字列リテラルについて詳しくはリテラルとサフィックス §.逐語的文字列リテラルを参照してください。 また、正規表現要素としてのエスケープ文字については.NET Frameworkで使用できる正規表現 §.文字のエスケープを参照してください。

§1.2 Regexクラスを用いた文字列操作

Regexクラスではパターンにマッチするかどうかの判定だけでなく、パターンマッチングを用いた文字列操作を行うことができます。 Stringクラスでの文字列の検索・置換・分割といった操作と同様に、Regexクラスでも正規表現を使った検索・置換・分割等を行うことができます。

Regexクラスでは文字列処理に関する次のメソッドが用意されています。 各メソッドを使った具体例についてはリンク先の解説を参照してください。 なお、参考として、Stringクラスの相当するメソッドも併記しています。

正規表現を使った文字列操作と対応するメソッド
文字列操作 Regexクラスのメソッド メソッドの結果(戻り値の種類) 同等の操作を行うためのStringクラスのメソッド
正規表現にマッチする箇所があるかどうかを調べる IsMatchメソッド bool Contains, StartsWith, EndsWith
正規表現にマッチする最初の箇所のみを取得する Matchメソッド Matchクラス IndexOf, IndexOfAny
正規表現にマッチするすべての箇所を取得する Matchesメソッド MatchCollectionクラス (IndexOf, IndexOfAny)
正規表現にマッチする箇所で分割する Splitメソッド stringの配列 Split
正規表現にマッチする箇所を別の文字列に置換する Replaceメソッド string Replace
文字列操作 Regexクラスのメソッド メソッドの結果(戻り値の種類) 同等の操作を行うためのStringクラスのメソッド

これらのメソッドでは、必要に応じてRegexOptionsを指定することによってメソッドの動作を変更することが出来ます。 例えば、大文字小文字の違いを無視したり、正規表現の処理を複数行モードに切り替えたりすることができます。 オプションの詳細については§.正規表現エンジンの動作オプション (RegexOptions)にて解説します。

§1.3 Regexクラスのインスタンスメソッドと静的メソッド

Regexクラスにおけるパターンマッチングと文字列操作のメソッドは、静的メソッドとしても、インスタンスメソッドとしても呼び出すことができます。 インスタンスメソッドとして呼び出す場合は、正規表現(=Regexクラス)をインスタンス化してから呼び出します。

Regexクラスの静的メソッドを使ってパターンマッチングを行う
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 「".txt"で終わるかどうか」を表す正規表現
    var pattern = @"\.txt$";

    // 文字列が正規表現にマッチするかどうか調べる
    Console.WriteLine(Regex.IsMatch("sample.txt", pattern));

    Console.WriteLine(Regex.IsMatch("sample.dat", pattern));
  }
}
Regexクラスのインスタンスメソッドを使ってパターンマッチングを行う
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 「".txt"で終わるかどうか」を表す正規表現
    var r = new Regex(@"\.txt$");

    // 文字列が正規表現にマッチするかどうか調べる
    Console.WriteLine(r.IsMatch("sample.txt"));

    Console.WriteLine(r.IsMatch("sample.dat"));
  }
}
実行結果
True
False

正規表現をインスタンス化する場合、正規表現文字列をコンストラクタで指定します。 静的メソッドとインスタンスメソッドでは呼び出し方と結果に違いはありませんが、パフォーマンスと内部の動作には違いがあります。 正規表現を使用する際、正規表現文字列の解析が行われますが、インスタンス化する場合は解析された結果がインスタンス内で保持されます。 そのため、同じ正規表現を何度も使用するような状況の場合では、インスタンス化して正規表現を使用することが推奨されます。

このほか、正規表現実行エンジンの挙動の違いやパフォーマンス上の差異に関しては正規表現のキャッシュとコンパイル §.正規表現のキャッシュでも詳しく解説しています。

Regexコンストラクタでは、正規表現エンジンの動作オプションを指定するRegexOptionsや、パターンマッチングのタイムアウト時間を指定することもできます。


なお、一度インスタンス化した正規表現とRegexOptionsは、後から変更することはできません。 Regexインスタンスの作成時に指定された正規表現を取得するにはToStringメソッドを呼び出します。

§1.4 .NET Frameworkにおける正規表現

Regexクラスで実装されている正規表現エンジン、および使用される文法はPerlなどで使用される正規表現とほぼ互換のものとなっています。 そのため、他の言語で使用される正規表現は、多くの場合Regexクラスでもそのまま用いることができます。

一方、PerlやRuby, JavaScriptでの正規表現は言語の機能として提供されますが、.NET Frameworkでの正規表現は個々の言語に組み込まれた機能としてではなくクラスライブラリ(=Regexクラス)として提供されています。 そのため、.NET Frameworkのライブラリを使用できる言語であれば、どの言語でも共通して同一の正規表現要素を使用することができます。



§1.4.1 .NET Frameworkで使用できる正規表現要素

正規表現ではいくつかの記号が特別な意味を持ちます。 これはワイルドカードにおける*?と機能的に似たものと言えます。 正規表現ではこういった正規表現要素を組み合わせて一つのパターンを作成します。 Regexクラスを通して使用できる正規表現言語要素については.NET Frameworkで使用できる正規表現でも詳しく解説しますが、この中でも基本的なもの・よく使われるものを以下の表に抜粋しておきます。

代表的な正規表現要素
カテゴリ 正規表現要素 意味とマッチする箇所 パターンとマッチの例
文字クラス [chars] charsに含まれる任意の一文字
[^chars]とした場合はchars含まれない任意の一文字となる
[aeiou]

a, e, i, o, uいずれかの文字にマッチ
[first-last] firstからlastまでの範囲に含まれる任意の一文字
[^first-last]とした場合はfirstからlastまでの範囲に含まれない任意の一文字となる
[a-z0-9]

aからzまでと0から9までの文字にマッチ
大文字のA-Zなどはマッチしない
. 任意の一文字 a.c

aに続けて任意の一文字があり、さらにcが続く箇所にマッチ
マッチするのはaac, abc, axc…など
量指定子 * 直前の文字が0文字以上連続する箇所 abc*

a, bに続けてcが0文字以上連続する箇所にマッチ
マッチするのはab, abc, abcc…など
+ 直前の文字が1文字以上連続する箇所 abc+

a, bに続けてcが1文字以上連続する箇所にマッチ
マッチするのはabc, abcc, abccc…など
? 直前の文字が0または1文字連続する箇所 abc?

a, bに続けてcが続くか、または続かない箇所にマッチ
マッチするのはabまたはabc
{n,m} 直前の文字がn文字以上m文字以下連続する箇所
{n}とした場合はちょうどn文字が連続する箇所となる。
{n,}とした場合はn文字以上となる。
{,m}という記述はできない。 (n文字以下を表す表現できない。
abc{2,4}

a, bに続けてcが2~4文字続く箇所にマッチ
マッチするのはabcc, abccc, abcccc
グループ (regex) regexにマッチした箇所を一つのグループとしてキャプチャする
また、他の正規表現との優先順位を変更する際にも使用する

グループとキャプチャについては正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.グループ(Groupクラス)正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.Match・Group・Capture各クラスの関係とマッチ箇所でで解説します
([a-z]+)

[a-z]+にマッチする箇所を一つのグループとみなす
選択・条件分岐 | | で区切られた正規表現のいずれかに一致する箇所
左側から優先してマッチする
a|an|(T|t)he

マッチするのはa, an, the, Theのいずれか
アンカー ^ 文字列の先頭 ^x+の場合、次の文字列中の強調した箇所にマッチする

xx33xxx33xx
$ 文字列の末尾 x+$の場合、次の文字列中の強調した箇所にマッチする

xx33xxx33xx
文字クラス \w
\W
\wは単語に使用されるUnicode文字1文字
\Wは\w以外の任意の1文字

(\wにはアルファベット・かな・漢字・数字のほか、アンダーバーなどいくつかの記号も含まれる)
\wは、次の文字列中の強調した文字が該当する

abc123_+!&あいう漢字123一二三『!☆〆々
\d
\D
\dは10進表記の数字に使用されるUnicode文字1文字
\Dは\d以外の任意の1文字

(\dには半角数字だけでなく、全角数字も含まれる)
\dは、次の文字列中の強調した文字が該当する

abc123_+!&あいう漢字123一二三『!☆〆々
\p{category}
\P{category}
\pはcategoryのUnicodeカテゴリに該当する文字1文字
\Pは\p以外の任意の1文字(categoryのUnicodeカテゴリに該当しない文字1文字)
それぞれのカテゴリには、次の文字列中の強調した文字が該当する

\p{S} (記号)
abc123_+!&あいう漢字123一二三『!〆々

\p{P} (句読点)
abc123_+!&あいう漢字123一二三『!☆〆々

\p{IsHiragana} (ひらがな)
abc123_+!&あいう漢字123一二三『!☆〆々

\p{IsCJKUnifiedIdeographs} (CJK統合漢字)
abc123_+!&あいう漢字123一二三『!☆〆々
文字のエスケープ \char 文字charのエスケープ表現
正規表現要素で使われる文字を通常の文字としてエスケープする場合に用いる

(\t, \r, \n, \wなどはエスケープではなく文字・文字クラスとして扱われる点に注意)
\\\ (バックスラッシュ)を表す
カテゴリ 正規表現要素 意味とマッチする箇所 パターンとマッチの例

上記以外の正規表現要素や個々の要素の詳細については.NET Frameworkで使用できる正規表現で解説しています。

また、RegexOptionsの指定によっては動作が変わる正規表現要素も存在します。 具体例については§.正規表現エンジンの動作オプション (RegexOptions)を参照してください。

このほか、.NET Frameworkにおける正規表現および正規表現要素については以下のドキュメントで解説されています。

§1.5 その他の正規表現関連の機能

RegexOptions.Compiledを指定すると、正規表現が使用される前にパターンの解析とコンパイルが行われ、ILコードへと変換させることができ、これによって正規表現の処理速度を向上させることができます。 (正規表現のキャッシュとコンパイル §.正規表現のコンパイル (RegexOptions.Compiled))

さらにRegex.CompileToAssemblyメソッドを使用すると、正規表現をコンパイルしてライブラリアセンブリ(=DLL)を生成することができます。 生成したアセンブリを使用することにより、プリコンパイル済みの正規表現を使用することができます。 (正規表現のキャッシュとコンパイル §.正規表現のプリコンパイル (Regex.CompileToAssembly))

こういった.NET Frameworkの正規表現エンジン固有の機能については正規表現のキャッシュとコンパイルで解説しています。

§2 Regexクラス

ここではRegexクラスを使って正規表現を用いたパターンマッチングと各種メソッドによる文字列操作を行う方法について解説します。 なお、以下のサンプルコードではパターンマッチングに静的メソッドを用いていますが、インスタンスメソッドを用いた場合でも同様に機能します。 実際にRegexクラスによるパターンマッチングを行うコードを記述する際は、静的メソッドとインスタンスメソッドの特性の違いを考慮の上、目的に応じて使い分けを行ってください。

§2.1 正規表現を用いた文字列の探索

正規表現を用いて文字列の探索を行う際、正規表現にマッチする箇所があるかどうかだけを判定したい場合はRegex.IsMatchメソッドを使います。 マッチする箇所のインデックスや実際にマッチした部分の文字列を参照したい場合はRegex.Matchメソッド、マッチする箇所すべてを列挙したい場合はRegex.Matchesメソッドを使います。 Matchメソッド・Matchesメソッドの戻り値を使うことにより、正規表現にマッチした部分の文字列に対して加工したりすることもできます。

§2.1.1 マッチ有無の判定 (Regex.IsMatch)

Regex.IsMatchメソッドは、指定された文字列に対して正規表現にマッチする箇所があるかどうかの判定を行います。 String.Containsなどのメソッドが指定された文字列を含むかどうかを返すのと同様、Regex.IsMatchでは指定された正規表現にマッチする文字列を含むかどうかを返します。

次の例では、IsMatchメソッドを使ってファイル名のうち拡張子が".txt"または".doc"のものだけを表示しています。 比較として、String.EndsWithメソッドを使って同等の処理を実装した例も併記します。

(この例で用いている正規表現"\.(txt|doc)$"は、「ピリオド("\.")に続けて、txtまたはdoc("(txt|doc)")が続き、その後に文字列の末尾("$")が表れる箇所」を表す正規表現です。)

Regex.IsMatchメソッドを使って正規表現にマッチする文字列を含むかどうか判定する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "sample.txt.bak",
      "sample.cs",
      "sample.doc",
      "test.txt",
    };

    foreach (var file in files) {
      // ファイル名のうち、正規表現にマッチするものを表示する
      if (Regex.IsMatch(file, @"\.(txt|doc)$"))
        Console.WriteLine(file);
    }
  }
}
String.EndsWithメソッドを使って条件に一致する文字列を含むかどうか判定する
using System;


class Sample {
  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "sample.txt.bak",
      "sample.cs",
      "sample.doc",
      "test.txt",
    };

    foreach (var file in files) {
      // ファイル名のうち、条件に一致するものを表示する
      if (file.EndsWith(".txt") || file.EndsWith(".doc"))
        Console.WriteLine(file);
    }
  }
}
実行結果
sample.txt
sample.doc
test.txt

このように、正規表現では単純な文字列の一致だけではなく、OR条件式や文字列の末尾での一致といった付加条件も記述することができます。 Stringクラスのメソッドでは複雑になるような文字列処理でも、正規表現を用いればシンプルに記述できるようになります。

String.Contains等のメソッドでStringComparisonを指定できるのと同様に、Regex.IsMatch等のメソッドでも動作オプションを変更するためのRegexOptionsを指定することができます。

パスやファイル名から拡張子部分を抽出するにはPath.GetExtensionメソッドを使うことができます。

§2.1.2 マッチ箇所の探索 (Regex.Match)

Regex.Matchメソッドは、指定された文字列に対して正規表現にマッチする箇所を探索して取得します。 String.IndexOfなどのメソッドが指定された文字列の位置(インデックス)のみを返すのに対し、Regex.Matchでは指定された正規表現にマッチする文字列の位置(インデックス)と、実際にマッチした部分の文字列を返します。

Regex.Matchメソッドは、戻り値としてMatchインスタンスを返します。 Succsssプロパティを参照するとMatchメソッドで正規表現にマッチする文字列が見つかったかどうかを取得できます。 これはIsMatchメソッドでマッチする箇所があるかどうか判定した場合の結果と同じ値になります。

マッチする箇所があった場合、Valueプロパティを参照すると実際に正規表現にマッチした部分の文字列が取得できます。 同様に、Indexプロパティでマッチした部分の位置(インデックス)、Lengthプロパティでその長さを取得できます。

次の例では、Matchメソッドを使ってファイル名のうち拡張子の部分を探索し、マッチした場合はその拡張子を表示しています。 この例では、.(ピリオド)以降を拡張子とみなす正規表現を使っています。

(この例で使用している正規表現"\..*"は、「ピリオド("\.")に続けて、任意の文字(".")が、0文字以上続く("*")箇所」を表す正規表現です。)

Regex.Matchメソッドを使って文字列中の正規表現にマッチする部分を取得する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "sample.txt.bak",
      "sample.cs",
      "test.txt",
      "test.jpeg",
      "README",
    };

    foreach (var file in files) {
      Console.Write("{0,-20} ", file);

      // 正規表現にマッチする箇所を探索
      var m = Regex.Match(file, @"\..*");

      if (m.Success)
        // マッチする箇所があった場合、マッチした部分の文字列と、インデックス・長さを表示する
        Console.WriteLine("{0,-10} ({1}, {2})", m.Value, m.Index, m.Length);
      else
        // マッチする箇所がなかった場合
        Console.WriteLine("?");
    }
  }
}
実行結果
sample.txt           .txt       (6, 4)
sample.txt.bak       .txt.bak   (6, 8)
sample.cs            .cs        (6, 3)
test.txt             .txt       (4, 4)
test.jpeg            .jpeg      (4, 5)
README               ?

マッチした結果からグループキャプチャを参照するには、Groupsプロパティを参照します。 マッチグループキャプチャ、およびそれぞれに該当するクラスについては正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.Match・Group・Capture各クラスの関係とマッチ箇所で解説しています。

マッチした文字列・インデックスの参照のほか、Resultメソッドを使ってマッチ箇所を別の文字列に置換したりすることもできます。 また、Matchメソッドでは最初にマッチした箇所が返されますが、NextMatchメソッドを使って次のマッチ箇所を取得することもできます。 これらについて詳しくは正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.マッチ箇所の参照および操作(Matchクラス)で解説しています。

パスやファイル名から拡張子部分を抽出するにはPath.GetExtensionメソッドを使うことができます。

§2.1.3 マッチ箇所の列挙・探索 (Regex.Matches)

Regex.Matchメソッドが正規表現にマッチする最初の箇所を返すのに対し、Regex.Matchesメソッドマッチする全ての箇所を返します。

Matchesメソッドはマッチする全ての箇所をMatchクラスのコレクション型MatchCollectionで返します。 このコレクションを列挙することにより、マッチした個々の箇所に対応するMatchインスタンスを参照することができます。 マッチする箇所がなければ、空のコレクションが返されます。

次のコードでは、正規表現を使って文字列中に含まれる4文字以上の単語をすべて探索し、表示しています。

(この例で用いている正規表現"\w{4,}"は、「アルファベット・かな・漢字などの文字("\w")が、4文字以上続く("{4,}")箇所」を表す正規表現です。)

Regex.Matchesメソッドを使って文字列中の正規表現にマッチする部分をすべて取得して列挙する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    string text = "The quick brown fox jumps over the lazy dog すばしっこい 茶色の 狐は のろまな 犬を 飛び越える";

    // 正規表現にマッチする箇所をすべて取得する
    MatchCollection matches = Regex.Matches(text, @"\w{4,}");

    // マッチした箇所をひとつずつ列挙して表示する
    foreach (Match m in matches) {
      Console.WriteLine("{0,-10} ({1}, {2})", m.Value, m.Index, m.Length);
    }
  }
}
実行結果
quick      (4, 5)
brown      (10, 5)
jumps      (20, 5)
over       (26, 4)
lazy       (35, 4)
すばしっこい     (44, 6)
のろまな       (58, 4)
飛び越える      (66, 5)

Matchインスタンスからマッチ結果を取得する方法については§.マッチ箇所の探索 (Regex.Match)を参照してください。

Matchesメソッドが返すMatchCollectionクラスは、.NET Framework 4.5の時点でもIEnumerable<Match>を実装しておらず非ジェネリックコレクションとなっています。 列挙を行う際には必要に応じてCast<Match>()メソッドを用いるなど、扱いに注意してください。

§2.2 正規表現を用いた文字列の加工

Regexクラスでは、正規表現にマッチする箇所の探索だけでなく、マッチ箇所を使った文字列の加工を行うこともできます。 マッチした箇所を別の文字列に置換するにはRegex.Replaceメソッド、マッチした箇所で文字列を分割するにはRegex.Splitメソッドを使うことができます。

§2.2.1 マッチ箇所の置換 (Regex.Replace)

Regex.Replaceメソッドを使うと、正規表現にマッチする箇所を探索し、マッチした部分を別の文字列に置き換えることができます。 別の文字列への単純な置換だけでなく、マッチした文字列を置換後の文字列として使用することができるほか、デリゲートを用いて置換時の動作を定義することもできます。

§2.2.1.1 別の文字列への置換

Regex.Replaceメソッドでは、引数に正規表現と置換後の文字列を指定することによって、正規表現にマッチした箇所を別の文字列へと置き換えることができます。

次の例では、正規表現"\..*''にマッチする箇所を新しい文字列".bak"に置換することにより、ファイル名の拡張子をすべて.bakに変更しています。

Regex.Replaceメソッドを使って正規表現にマッチする箇所を別の文字列に置き換える
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "sample.txt.bak",
      "sample.cs",
      "test.txt",
      "test.jpeg",
      "README",
    };

    foreach (var file in files) {
      Console.Write("{0,-20} => ", file);

      // 正規表現を用いてファイル名の拡張子に該当する箇所を".bak"に置き換える
      var bakFile = Regex.Replace(file, @"\..*", ".bak");

      Console.WriteLine(bakFile);
    }
  }
}
実行結果
sample.txt           => sample.bak
sample.txt.bak       => sample.bak
sample.cs            => sample.bak
test.txt             => test.bak
test.jpeg            => test.bak
README               => README

Regex.Replaceメソッドでは、置換後の文字列において$で始まる文字列が正規表現要素として解釈される場合があります。 正規表現要素として解釈されないようにするには、$$$とエスケープする必要があります。 Regex.Replaceメソッドで$を含む文字列へと置換する場合は注意してください。

置換の正規表現要素については後述の§.マッチした文字列への置換 (置換の正規表現要素)および.NET Frameworkで使用できる正規表現 §.置換を参照してください。

Match.Resultメソッドを用いることでもマッチ箇所の置換をおこなうことができます。 詳しくは正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.Match.Resultメソッドで解説しています。

パスやファイル名の拡張子部分を置換するにはPath.ChangeExtensionメソッドを使うこともできます。

§2.2.1.2 マッチした文字列への置換 (置換の正規表現要素)

Regex.Replaceメソッドでは、置換後の文字列も正規表現として解釈されます。 具体的には、置換文字列中の$で始まる文字列が置換の正規表現要素と解釈されます。 これを用いると、正規表現にマッチした文字列を全く別の文字列に置き換えるだけでなく、元の文字列を使った別の文字列に置き換えることができます。

置換の正規表現要素の一例として、"$0"は正規表現にマッチした文字列へと置き換えられます。 次の例では、拡張子の正規表現にマッチした箇所($0)に".bak"を追加して置換しています。

Regex.Replaceメソッドと置換の正規表現を用いてマッチした文字列へと置き換える
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "sample.txt.bak",
      "sample.cs",
      "test.txt",
      "test.jpeg",
      "README",
    };

    foreach (var file in files) {
      Console.Write("{0,-20} => ", file);

      // 正規表現"\..*"で探索し、マッチした箇所に".bak"を付加して置き換える
      var bakFile = Regex.Replace(file, @"\..*", "$0.bak");

      Console.WriteLine(bakFile);
    }
  }
}
実行結果
sample.txt           => sample.txt.bak
sample.txt.bak       => sample.txt.bak.bak
sample.cs            => sample.cs.bak
test.txt             => test.txt.bak
test.jpeg            => test.jpeg.bak
README               => README

Match.Resultメソッドを用いることでも置換の正規表現要素を使った置換をおこなうことができます。 詳しくは正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.Match.Resultメソッドで解説しています。


置換の正規表現要素を使った別の例を使って見てみます。 次の例では、3文字の単語("\b\w{3}\b")にマッチした箇所("$0")に対して、山括弧で括って置換する("<$0>")ことでマッチ箇所を強調表示しています。 なお、この例では"\b"を指定することにより、正規表現"\w{3}"単語の途中にはマッチしないようにしています。

Regex.Replaceメソッドと置換の正規表現要素を使ってマッチ箇所を強調表示する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = "The quick brown fox jumps over the lazy dog";

    // 正規表現にマッチした箇所を山括弧で括った文字列に置換し、強調表示する
    Console.WriteLine(Regex.Replace(text, @"\b\w{3}\b", "<$0>"));
  }
}
実行結果
<The> quick brown <fox> jumps over <the> lazy <dog>

§2.2.1.3 デリゲートを用いた置換 (MatchEvaluator)

Regex.Replaceメソッドでは、別の文字列への置換正規表現での置換のほか、マッチ箇所を検証して変換するメソッドを使って置換することもできます。 Regex.ReplaceメソッドにMatchEvaluatorデリゲートを指定することで、そのメソッドを呼び出した結果を置換文字列とすることができます。 置換時の動作をメソッドとして細かく規定することができるため、置換の正規表現要素だけでは記述できないような置換を行うことができます。

MatchEvaluatorデリゲートにはマッチ箇所の置換を行うメソッド(Matchクラスを引数にとり、置換後の結果となる文字列を返すメソッド)を指定します。 Replaceメソッドを呼び出してマッチする箇所が見つかると、マッチした箇所に該当するMatchインスタンスがMatchEvaluatorに渡されます。 MatchEvaluatorが返す文字列がマッチ箇所に置換され、Replaceメソッドの結果として返されます。

次の例では、Replaceメソッドを用いて拡張子にあたる部分を探索し、MatchEvaluatorデリゲートによって拡張子部分を".bak"に置き換えています。

Regex.ReplaceメソッドとMatchEvaluatorを使ってマッチ箇所の文字列を置換する
using System;
using System.Text.RegularExpressions;

class Sample {
  // マッチした箇所に置き換える文字列を返すメソッド
  static string ChangeExtension(Match m)
  {
    // 引数mにはRegex.Replaceメソッドの呼び出しでマッチした箇所が設定されている

    if (m.Value.EndsWith(".bak"))
      // マッチした箇所が".bak"で終わる場合は、そのままにする
      return m.Value;
    else
      // そうでなければ、".bak"を追加する
      return m.Value + ".bak";
  }

  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "sample.txt.bak",
      "sample.cs",
      "test.txt",
      "test.jpeg",
      "README",
    };

    foreach (var file in files) {
      Console.Write("{0,-20} => ", file);

      // 正規表現にマッチした箇所をメソッドChangeExtensionが返す結果で置換する
      var bakFile = Regex.Replace(file, @"\..*", ChangeExtension);

      Console.WriteLine(bakFile);
    }
  }
}
実行結果
sample.txt           => sample.txt.bak
sample.txt.bak       => sample.txt.bak
sample.cs            => sample.cs.bak
test.txt             => test.txt.bak
test.jpeg            => test.jpeg.bak
README               => README

Regex.Replaceメソッドで置換後の文字列を指定した場合、この文字列は単純な文字列ではなく正規表現として解釈され、$ で始まる正規表現要素を指定することが出来ます。


MatchEvaluatorに渡されるMatchインスタンスは、置換の正規表現要素$0に相当する値となっています。 以下の例では、MatchEvaluatorを使った置換と、それと同等の置換を正規表現要素を用いて記述したものを併記しています。 どちらも同じ結果を出力します。 (この例で使用している正規表現については§.マッチした文字列への置換 (置換の正規表現要素)の解説を参照してください。)

Regex.ReplaceメソッドとMatchEvaluatorを使ってマッチ箇所を強調表示する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    string text = "The quick brown fox jumps over the lazy dog";

    // MatchEvaluatorを使った置換
    Console.WriteLine(Regex.Replace(text, @"\b\w{3}\b", m => "<" + m.Value + ">"));

    // 置換の正規表現要素を使った置換 (上記と同等のコード)
    Console.WriteLine(Regex.Replace(text, @"\b\w{3}\b", "<$0>"));
  }
}
実行結果
<The> quick brown <fox> jumps over <the> lazy <dog>
<The> quick brown <fox> jumps over <the> lazy <dog>

§2.2.2 マッチ箇所での分割 (Regex.Split)

Regex.Splitメソッドを使うと、正規表現にマッチする箇所を探索し、マッチした部分を区切りとして文字列を分割することができます。 言い換えると、文字列分割の区切り文字として任意の正規表現を用いることができます。 Regex.SplitメソッドはString.Splitメソッドと同様、分割した結果を文字列型の配列で返します。

次の例では、句読点("\p{P}")を区切りとみなして文字列を分割しています。

Regex.Splitメソッドを使って正規表現にマッチした箇所で文字列を分割する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = "abc,def.ghi!あいう、アイウ。かな「漢字」カナ,123.123";

    // 正規表現にマッチした箇所で文字列を分割する
    var words = Regex.Split(text, @"\p{P}");

    // 分割した文字列を列挙する
    foreach (var word in words) {
      Console.WriteLine(word);
    }
  }
}
実行結果
abc
def
ghi
あいう
アイウ
かな
漢字
カナ
123
123

この例で使用している正規表現"\p{P}"Pは、「すべての句読点」を表すUnicodeカテゴリを意味します。 このほかにも「全ての制御文字」を表すCや、「全ての記号」を表すS、「記号・数学」を表すSmなどを使うこともできます。 詳しくは.NET Frameworkで使用できる正規表現 §.文字クラス等を参照してください。


次の例では、Regex.Splitメソッドを用いて、\r\n, \r, \nのいずれかを改行とみなして文字列を行毎に分割しています。

Regex.Splitメソッドを使って複数の改行文字を区切りとして文字列を行ごとに分割する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = "1行目\r\n2行目\n3行目\r4行目\n\r6行目";

    // "\r\n", "\r", "\n"を改行文字として、文字列を行ごとに分割する
    var lines = Regex.Split(text, @"\r\n|\r|\n");

    foreach (var line in lines) {
      Console.WriteLine(line);
    }
  }
}
実行結果
1行目
2行目
3行目
4行目

6行目

上記の例において、区切りの正規表現を"(\r\n|\r|\n)"のようにカッコで括ってグループ化すると、Regex.Splitメソッドは正規表現にマッチした部分の文字列も含めて返すようになります。 この動作について詳しくは正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.グループ化された正規表現による分割 (Regex.Split)で解説しています。

\r, \n, \r\nが混在する文字列の分割はTextReaderを使うことでも行えます。 例として、StringReaderを使った行分割は次のように行うことができます。

using System;
using System.IO;

class Sample {
  static void Main()
  {
    var text = "1行目\r\n2行目\n3行目\r4行目\n\r6行目";
    var r = new StringReader(text);

    for (;;) {
      var line = r.ReadLine();

      if (line == null) break;

      Console.WriteLine(line);
    }
  }
}

TextReaderを使った行ごとの読み込みに関してはStreamReaderクラス・StreamWriterクラス §.1行ずつの読み込み (ReadLine)、StringReaderについてはStringReaderクラス/StringWriterクラス §.StringReaderクラスで解説しています。

§2.3 正規表現文字列の取得 (Regex.ToString)

Regexクラスは不変であるため、インスタンスとして作成した正規表現は後から変更することは出来ません。 Regexインスタンスが表す正規表現文字列(Regexコンストラクタに渡した正規表現文字列)は、Regex.ToStringメソッドを呼び出すことで取得できます。

Regex.ToStringメソッドでRegexインスタンスが表す正規表現文字列を取得する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var txtFilePattern = new Regex(@"\.txt$");

    // ToStringメソッドを呼び出してRegexインスタンスの正規表現文字列を取得する
    Console.WriteLine(txtFilePattern.ToString());
  }
}
実行結果
\.txt$

§2.4 正規表現要素のエスケープ・アンエスケープ (Regex.Escape, Regex.Unescape)

Regex.Escapeメソッドを使うと、正規表現要素をエスケープした文字列を取得することができます。 コード中に静的に正規表現を記述する場合は事前にエスケープ済みの状態で記述することができますが、例えばユーザー入力による検索文字列の指定など、実行時にエスケープする必要が生じるような場合にはこのメソッドを用いることができます。

Regex.Escapeメソッドで文字列中の正規表現要素をエスケープする
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 文字列中の記号のうち正規表現要素として使われるものをエスケープする
    var str = @"().*[]!#%$";

    Console.WriteLine(str);
    Console.WriteLine(Regex.Escape(str));
  }
}
実行結果
().*[]!#%$
\(\)\.\*\[]!\#%\$

このメソッドでは、置換の正規表現要素を表す$をエスケープしても$$とはならず、\$とエスケープされるようです。


逆に、エスケープされている正規表現要素をアンエスケープ(エスケープ解除)するにはRegex.Unescapeメソッドを使います。 結果はEscapeメソッドと逆になります。

Regex.Unscapeメソッドで文字列中の正規表現要素をアンエスケープする
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 文字列中のエスケープされている記号をアンエスケープする
    var str = @"\(\)\.\*\%\!$$\$";

    Console.WriteLine(str);
    Console.WriteLine(Regex.Unescape(str));
  }
}
実行結果
\(\)\.\*\%\!$$\$
().*%!$$$

このメソッドでは、エスケープされた置換の正規表現要素を表す$$はエスケープされているとは解釈されず、$$のままとなるようです。 \$$にアンエスケープされるようです。

§3 正規表現エンジンの動作オプション (RegexOptions)

RegexコンストラクタまたはRegexクラスの各メソッド呼び出し時にRegexOptions列挙体を指定すると、正規表現エンジンの動作を変更することができます。 例えば、RegexOptions.IgnoreCaseを指定すれば大文字小文字の違いを無視してパターンマッチングを行うようになり、またRegexOptions.Multilineを指定すると複数行モードとなり、文字列の先頭を表す正規表現要素^が全ての行頭にマッチするようになります。

一部のオプションは正規表現内でインラインオプションとしても記述することができ、RegexOptionsの指定の代わりに使うことができます。 正規表現全体ではなく、正規表現の一部分のみにオプションを適用したい場合にもインラインオプションを使用することができます。 例えば、正規表現"[a-z]+[0-9]+"のアルファベット部分のみで大文字小文字を無視するようにするにはインラインオプションiを使用して"(?i:[a-z]+)[0-9]+"と記述します。

RegexOptions(またはインラインオプション)は複数個を組み合わせて指定することができます。 特に指定しない場合は、RegexOptions.Noneを指定した場合と同じ動作となります。

以下はRegexOptionsで指定できる値と、その指定によって変わる動作に一覧です。

RegexOptionsで指定できる値と動作
インラインオプション 動作 解説・詳細・具体例等
None - デフォルトの動作。
IgnoreCase (?i:subexpression) 大文字小文字の違いを無視する。 §.大文字小文字の違いの無視 (RegexOptions.IgnoreCase)
Multiline (?m:subexpression) 複数行モードにする。
^は文字列の先頭に加え行頭にもマッチ、$は文字列の末尾に加え行末にもマッチするようになる。
§.複数行モード (RegexOptions.Multiline)
.NET Frameworkで使用できる正規表現 §.RegexOptions.Multilineと^, \Aの動作の違い
.NET Frameworkで使用できる正規表現 §.RegexOptions.Multilineと$, \z, \Zの動作の違い
Singleline (?s:subexpression) 単一行モードにする。
.\nにもマッチするようになる。
§.単一行モード (RegexOptions.Singleline)
.NET Frameworkで使用できる正規表現 §.RegexOptions.Singlelineと"."の動作の違い
ExplicitCapture (?n:subexpression) 明示的に名前または番号を指定したグループのみをキャプチャする。
通常のグループ(subexpression)はキャプチャされないグループ(?:subexpression)と同様に扱われる。
正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.明示的なグループ化 (RegexOptions.ExplicitCapture)
IgnorePatternWhitespace (?x:subexpression) エスケープされない空白をパターンから除外し、#以降を行末までのコメントとして扱うように変更する。 .NET Frameworkで使用できる正規表現 §.コメント
CultureInvariant - インバリアントカルチャを使った比較を行う。
(デフォルトでは現在のカルチャに指定されているカルチャに従って比較が行われる)
§.インバリアントカルチャによる比較 (RegexOptions.CultureInvariant)
カルチャの基本とカルチャ情報 §.インバリアントカルチャ
文字列と比較オプション・カルチャの並べ替え規則 §.StringComparison列挙型
RightToLeft - 探索の方向を右から左に反転する。
キャプチャされるグループに割り当てられるインデックスも逆になる。
§.探索方向の反転 (RegexOptions.RightToLeft)
Compiled - 使用される前に正規表現をコンパイルし、ILコードを生成する。 正規表現のキャッシュとコンパイル §.正規表現のコンパイル (RegexOptions.Compiled)
ECMAScript - ECMAScript互換の動作にする。
\w[a-zA-Z0-9_]\d[0-9]と等価になるなど、いくつかの正規表現要素の意味が変わる。
IgnoreCase、Multiline、Compiledと組み合わせて使用する必要ある。

§3.1 大文字小文字の違いの無視 (RegexOptions.IgnoreCase)

RegexOptionsを指定しないデフォルトの状態では、正規表現エンジンは大文字小文字の違いを区別して扱いますが、RegexOptions.IgnoreCaseを指定すると大文字小文字の違いを無視するようになります。 これは、String.ContainsなどのメソッドでStringComparison.OrdinalIgnoreCaseを指定して大文字小文字の違いを無視した比較を行うようにすることに相当します。

次の例では、正規表現を用いて拡張子が".txt"のファイルだけを抽出して表示しています。 拡張子の大文字小文字の違いを無視するために、RegexOptions.IgnoreCaseを指定しています。

RegexOptions.IgnoreCaseを指定して大文字小文字を無視したパターンマッチングを行う
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var files = new string[] {
      "sample.txt",
      "Sample.Txt",
      "sample.txt.bak",
      "sample.cs",
      "test.txt",
      "TEST.TXT",
      "test.HTML",
    };

    foreach (var file in files) {
      // 拡張子が.txtのファイル名だけ表示する(大文字小文字の違いを無視する)
      if (Regex.IsMatch(file, @"\.txt$", RegexOptions.IgnoreCase))
        Console.WriteLine(file);
    }
  }
}
実行結果
sample.txt
Sample.Txt
test.txt
TEST.TXT

§3.2 複数行モード (RegexOptions.Multiline)

RegexOptions.Multilineを指定すると、正規表現エンジンは複数行モードで動作するようになります。 複数行モードでは、文字列の先頭・末尾を表す正規表現要素^, $が、それぞれ行頭・行末にもマッチするようになります。

次の例では、正規表現を用いて行頭の空白を削除しています。 同じ正規表現を用いていても、RegexOptions.Multilineを指定した場合とそうでない場合で結果が異なっている点に注目してください。

RegexOptions.Multilineを指定して複数行モードでパターンマッチングを行う
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = @"static void Main() {
  for (int i = 0; i < 10; i++) {
    Console.WriteLine(i);
  }
}";

    // 行頭(^)に続く空白を空文字に置き換えて削除する
    // (複数行モードなので"^"はすべての行頭にマッチする)
    Console.WriteLine("[Regex.Replace(RegexOptions.Multiline)]");
    Console.WriteLine(Regex.Replace(text, @"^\s+", string.Empty, RegexOptions.Multiline));
    Console.WriteLine();

    // 文字列の先頭(^)に続く空白を空文字に置き換えて削除する
    // (複数行モードではないので"^"は文字列の先頭にしかマッチしない)
    Console.WriteLine("[Regex.Replace(RegexOptions.None)]");
    Console.WriteLine(Regex.Replace(text, @"^\s+", string.Empty, RegexOptions.None));
  }
}
実行結果
[Regex.Replace(RegexOptions.Multiline)]
static void Main() {
for (int i = 0; i < 10; i++) {
Console.WriteLine(i);
}
}
[Regex.Replace(RegexOptions.None)]
static void Main() {
  for (int i = 0; i < 10; i++) {
    Console.WriteLine(i);
  }
}

RegexOptions.Multilineを指定した場合に動作が変わる正規表現要素と、その動作の違いについての詳細は、.NET Frameworkで使用できる正規表現 §.RegexOptions.Multilineと^, \Aの動作の違いおよび.NET Frameworkで使用できる正規表現 §.RegexOptions.Multilineと$, \z, \Zの動作の違いを参照してください。

§3.3 単一行モード (RegexOptions.Singleline)

RegexOptions.Singlelineを指定すると、正規表現エンジンは単一行モードで動作するようになります。 単一行モードでは、任意の一文字を表す正規表現要素.\nにもマッチするようになります。 言い換えると、単一行モードでは改行を含む文字列でも、それを改行とみなさず他の文字と同等に処理します。

次の例では、正規表現を用いて"fox"から始まり"dog"で終わる部分の文字列を抽出しています。 同じ正規表現を用いていても、RegexOptions.Singlelineを指定した場合とそうでない場合で結果が異なっている点に注目してください。

RegexOptions.Singlelineを指定して単一行モードでパターンマッチングを行う
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = @"The quick brown 
fox jumps over 
the lazy dog";

    // foxから始まり、任意の文字列を挟んでdogで終わる部分の文字列を表示する
    // (単一行モードなので、間に改行\nを含んでいてもマッチする)
    Console.WriteLine("[Regex.Match(RegexOptions.Singleline)]");
    Console.WriteLine(Regex.Match(text, @"fox.+dog", RegexOptions.Singleline));
    Console.WriteLine();

    // foxから始まり、任意の文字列を挟んでdogで終わる部分の文字列を表示する
    // (単一行モードではないので、間に改行\nを含んでるとマッチしない)
    Console.WriteLine("[Regex.Match(RegexOptions.None)]");
    Console.WriteLine(Regex.Match(text, @"fox.+dog", RegexOptions.None));
  }
}
実行結果
[Regex.Match(RegexOptions.Singleline)]
fox jumps over 
the lazy dog

[Regex.Match(RegexOptions.None)]

.は、None, Singleline, Multilineのいずれを指定した場合でも常に\rにマッチします。

RegexOptions.Singlelineを指定した場合に動作が変わる正規表現要素とその動作の違いについては、.NET Frameworkで使用できる正規表現 §.RegexOptions.Singlelineと"."の動作の違いも参照してください。

§3.4 インバリアントカルチャによる比較 (RegexOptions.CultureInvariant)

RegexOptions.CultureInvariantを用いると、文字の比較にインバリアントカルチャを用いた比較を行うようにすることができます。

次の例では、正規表現を使って単語がiで始まるかどうかを調べています。 比較の際、インラインオプション(?i:)により、大文字小文字の違いは無視されます。

RegexOptions.CultureInvariantを指定してインバリアントカルチャによる比較を行う
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;

class Sample {
  static void Main()
  {
    var text = "Internet";
    var pattern = @"(?i:i(\w+))";

    // 現在のカルチャを表示
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    // RegexOptions.CultureInvariantを指定した場合とそうでない場合で結果を比較
    Console.WriteLine(Regex.IsMatch(text, pattern));
    Console.WriteLine(Regex.IsMatch(text, pattern, RegexOptions.CultureInvariant));
    Console.WriteLine();

    // 現在のカルチャを"tr-TR"に変更
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");

    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    // RegexOptions.CultureInvariantを指定した場合とそうでない場合で結果を比較
    Console.WriteLine(Regex.IsMatch(text, pattern));
    Console.WriteLine(Regex.IsMatch(text, pattern, RegexOptions.CultureInvariant));
    Console.WriteLine();
  }
}
実行結果
ja-JP
True
True

tr-TR
False
True

この例での入力文字列"Internet"iで始まる単語のため、カルチャがja-JPの場合はRegexOptions.CultureInvariantを指定しているかどうかに関わらず正規表現にマッチします。 しかしカルチャをtr-TR(トルコ語/トルコ)に変更すると、RegexOptions.CultureInvariantを指定しないとマッチしません。

トルコ語においてはiIが大文字小文字の関係ではなく異なる文字として扱われるため、正規表現には一致しなくなります(参考、Internetのトルコ語表記: İnternet)。 この例のように、カルチャによって異なる結果とならないようにするには、RegexOptions.CultureInvariantを指定する必要があります。

カルチャや、カルチャによる動作結果の違いなどについてはカルチャの基本とカルチャ情報で解説しています。

RegexOptions.CultureInvariantの動作に関しては文字列と比較オプション・カルチャの並べ替え規則 §.StringComparison列挙型も合わせて参照してください。

§3.5 探索方向の反転 (RegexOptions.RightToLeft)

RegexOptions.RightToLeftを用いると、正規表現エンジンの文字列探索方向を「左から右」ではなく「右から左」、つまり文字列の末尾から先頭への方向へ逆転することができます。 これは、文字列の先頭から末尾へ探索するString.IndexOfに対して、String.LastIndexOfを用いて文字列の末尾から先頭へ探索するように変えることに相当します。

RegexOptions.RightToLeftを指定して正規表現の探索方向を文字列の末尾から先頭の方向へ反転する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = "The quick brown fox jumps over the lazy dog";

    Console.WriteLine(text);
    Console.WriteLine();

    // 3文字の単語をすべて列挙する
    // RegexOptions.RightToLeftを指定しているので、文字列の後側にあるものから列挙される
    Console.WriteLine("[RegexOptions.RightToLeft]");
    foreach (Match m in Regex.Matches(text, @"\b\w{3}\b", RegexOptions.RightToLeft)) {
      Console.WriteLine(m.Value);
    }

    // 3文字の単語をすべて列挙する
    // RegexOptions.RightToLeftを指定していないので、文字列の先頭にあるものから列挙される
    Console.WriteLine("[RegexOptions.None]");
    foreach (Match m in Regex.Matches(text, @"\b\w{3}\b", RegexOptions.None)) {
      Console.WriteLine(m.Value);
    }
  }
}
実行結果
The quick brown fox jumps over the lazy dog

[RegexOptions.RightToLeft]
dog
the
fox
The
[RegexOptions.None]
The
fox
the
dog

RegexOptions.RightToLeftはあくまで探索方向を反転するものであって、正規表現の解釈方向を反転するものではありません。 RegexOptions.RightToLeftを指定しても正規表現の末尾側からマッチすることはありません。

RegexOptions.RightToLeftの指定によってキャプチャされる順序も逆転します。 キャプチャについては正規表現の構造化および処理(マッチ・グループ・キャプチャ) §.キャプチャ(Captureクラス)を参照してください。

§3.6 インラインでのオプション指定

いくつかのオプションは正規表現要素としてインラインで指定することもできます。 例えばRegexOptions.IgnoreCaseは、インラインオプションとして(?i:regex)と記述できます。 RegexOptionsでの指定では、メソッド呼び出し時に正規表現全体に対してオプションを適用することになりますが、インラインオプションでは正規表現の全体だけでなく一部分のみにオプションを適用することもできます。

次の例では、正規表現を使って拡張子が.txtかどうかを調べています。 大文字小文字の違いを無視するためにインラインオプションを指定しています。 比較のためにRegexOptionsを指定しる方法も記述しています。

インラインオプションを使ってRegexOptions.IgnoreCaseに相当するオプションを指定する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 拡張子が.txtかどうか調べる (大文字小文字の違いを意識する)
    Console.WriteLine(Regex.IsMatch("SAMPLE.TXT", @"\.txt$"));

    // 拡張子が.txtかどうか調べる (大文字小文字の違いを無視する・インラインオプション)
    Console.WriteLine(Regex.IsMatch("SAMPLE.TXT", @"(?i:\.txt$)"));

    // 拡張子が.txtかどうか調べる (大文字小文字の違いを無視する・上記と同等のコード)
    Console.WriteLine(Regex.IsMatch("SAMPLE.TXT", @"\.txt$", RegexOptions.IgnoreCase));
  }
}
実行結果
False
True
True

インラインオプションは有効化だけでなく無効化に用いることもできます。 例えば(?i:regex)はRegexOptions.IgnoreCaseを有効にしますが、(?-i:regex)とすれば無効にすることができます。 このほか、正規表現でのインラインオプションの記述方法などについては.NET Frameworkで使用できる正規表現 §.インラインオプションも参照してください。

§4 正規表現エンジンのタイムアウト

Regexクラスでは正規表現エンジンの実行をタイムアウトさせることができます。 IsMatchReplace等の静的メソッドでTimeSpanを指定するか、Regexインスタンスを作成する際にコンストラクタでTimeSpanを指定すると、その時間内にパターンマッチングが完了しない場合はタイムアウトしたとして例外をスローさせることができます。 タイムアウトした場合、例外としてRegexMatchTimeoutExceptionがスローされます。 正規表現エンジンのタイムアウトは.NET Framework 4.5以降で使用できます。

入力文字列が巨大な場合や、処理負荷の大きい正規表現を指定した場合などは、パターンマッチングの際にタイムアウトする可能性があります。 例えば次の正規表現は一見単純に見えますが、大量のバックトラッキングが発生するため、入力文字列の長さによってはタイムアウトします。 (タイムアウトしない場合は、入力文字列のaの数を増やすとタイムアウトするようになります)

RegexコンストラクタにTimeSpanを指定して正規表現エンジンにタイムアウトを設定する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    // 0.1秒でタイムアウトする正規表現
    var r = new Regex(@"^(a+)+$", RegexOptions.None, TimeSpan.FromSeconds(0.1));

    try {
      r.IsMatch("aaaaaaaaaaaaaaaaaaaaaa!");
    }
    catch (RegexMatchTimeoutException ex) {
      Console.Error.WriteLine("入力文字列'{0}'に対する正規表現'{1}'のパターンマッチングがタイムアウトしました", ex.Input, ex.Pattern);
    }
  }
}
実行結果
入力文字列'aaaaaaaaaaaaaaaaaaaaaa!'に対する正規表現'^(a+)+$'のパターンマッチングがタイムアウトしました

上記の例にもあるように、スローされた例外RegexMatchTimeoutExceptionのPatternプロパティを参照すると、タイムアウトした正規表現を取得することができ、同様にInputプロパティを参照するとタイムアウトの原因となった入力文字列を取得することができます。

デフォルトの状態(TimeSpanを指定しない場合)ではタイムアウトしません。 TimeSpanとしてRegex.InfiniteMatchTimeoutを指定すると、デフォルトの状態と同じになり、タイムアウトしないことを明示的に指定することができます。

なお、RegexコンストラクタでTimeSpanを設定した場合、設定したタイムアウト時間はRegex.MatchTimeoutプロパティを参照することで取得できます。

§5 正規表現と文字列操作の例

§5.1 子音で始まる単語の抽出

次の例で使用している正規表現"^[aeiou][a-z]*"は、母音で始まる単語にマッチします。 この正規表現は、文字列の先頭(^)に続けて母音([aeiou])が一文字あり、以降任意のアルファベットが0文字以上([a-z]*)続く文字列とマッチします。 ^が無い場合は、文字列の途中からでもマッチしてしまう点に注意してください。

正規表現を使って子音で始まる単語のみを抽出する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    string[] words = new string[] {
      "The",
      "quick",
      "brown",
      "fox",
      "jumps",
      "over",
      "the",
      "lazy",
      "dog",
    };

    foreach (string word in words) {
      Console.Write("{0,-20} ", word);

      if (Regex.IsMatch(word, "^[aeiou][a-z]*"))
        Console.WriteLine("O");
      else
        Console.WriteLine("X");
    }
  }
}
実行結果
The                  X
quick                X
brown                X
fox                  X
jumps                X
over                 O
the                  X
lazy                 X
dog                  X

§5.2 IPアドレス(IPv4)の抽出

次の例で使用している正規表現"\d{1,3}(\.\d{1,3}){3}(/\d{1,2})?"は、IPアドレス(IPv4)となりうる文字列にマッチします。 この正規表現は、1~3桁の数字(\d{1,3})に続けて、ピリオドと1~3桁の数字の組み合わせが三回続き((\.\d{1,3}){3})、場合によってはスラッシュと1~2桁の数字が後続する((/\d{1,2})?)文字列にマッチします。

この正規表現では、IPアドレスとしては不正な値の文字列にもマッチします。 具体的には、\dを使っているため全角数字にもマッチする、\d{1,3}には999などのIPアドレスとしてはありえない数字にもマッチする、^を使っていないため文字列の途中でもマッチするなど、完全ではない点に注意してください。

正規表現を使って文字列がIPアドレスの形式となっている箇所を含むかどうか判定する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    string[] words = new string[] {
      "0.0.0.0/8",
      "127.0.0.1",
      "192.168.1.1",
      "192.168.0.0/16",
      "255.255.255.255",
      "new IPAddress(\"192.168.1.1\")",
      "localhost",
      "192.168.000.000",
      "192.168.999.999",
      "192.168.999.999/99",
      "3.141593",
      "2010.11.11",
      "127.0.0.1",
    };

    foreach (string word in words) {
      Console.Write("{0,-30} ", word);

      if (Regex.IsMatch(word, @"\d{1,3}(\.\d{1,3}){3}(/\d{1,2})?"))
        Console.WriteLine("O");
      else
        Console.WriteLine("X");
    }
  }
}
実行結果
0.0.0.0/8                      O
127.0.0.1                      O
192.168.1.1                    O
192.168.0.0/16                 O
255.255.255.255                O
new IPAddress("192.168.1.1")   O
localhost                      X
192.168.000.000                O
192.168.999.999                O
192.168.999.999/99             O
3.141593                       X
2010.11.11                     X
127.0.0.1                      O

§5.3 単語を文字の分類ごとに抽出する

次の例で使用している正規表現"\p{Ll}|\p{Lu})+|\p{IsHiragana}+|\p{IsKatakana}+|\p{IsCJKUnifiedIdeographs}+"は、アルファベット・ひらがな・カタカナ・漢字が連続する箇所にマッチします。 この正規表現は、小文字または大文字の連続((\p{Ll}|\p{Lu})+)、ひらがなの連続(\p{IsHiragana}+)、カタカナの連続(\p{IsKatakana}+)、漢字の連続(\p{IsCJKUnifiedIdeographs}+)のいずれかにマッチします。

正規表現を使って文字列を同じ種類の文字ごとに区切って表示する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    string text = "Regexにマッチした箇所を一つのグループとしてキャプチャする";
    string pattern = @"(\p{Ll}|\p{Lu})+|\p{IsHiragana}+|\p{IsKatakana}+|\p{IsCJKUnifiedIdeographs}+";

    foreach (Match m in Regex.Matches(text, pattern)) {
      Console.WriteLine(m.Value);
    }
  }
}
実行結果
Regex
に
マッチ
した
箇所
を
一
つの
グループ
として
キャプチャ
する

次の例で使用している正規表現"[\u3040-\u30ff]+"は、ひらがなとカタカナが連続する箇所にマッチします。 この正規表現は、ひらがな(U+3040~U+309F)およびカタカナ(U+30A0~U+30FF)に該当するコードポイントの文字([\u3040-\u30ff])が1文字以上連続する(+)箇所にマッチします。

正規表現を使って文字列中のひらがなとカタカナが連続する部分を抽出する
using System;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    string text = "Regexにマッチした箇所を一つのグループとしてキャプチャする";
    string pattern = @"[\u3040-\u30ff]+";

    foreach (Match m in Regex.Matches(text, pattern)) {
      Console.WriteLine(m.Value);
    }
  }
}
実行結果
にマッチした
を
つのグループとしてキャプチャする

§5.4 全角英数を半角英数に変換する

次の例で使用している正規表現"(?<fwl>[a-z]+)|(?<fwu>[A-Z]+)|(?<fwd>[0-9]+)"は、次の3つの正規表現それぞれを名前付きグループとしてグループ化しています。

  1. (?<fwl>[a-z]+): [a-z]の範囲にある文字(全角小英字)が1文字以上続く箇所をグループ名fwlでグループ化
  2. (?<fwu>[A-Z]+): [A-Z]の範囲にある文字(全角大英字)が1文字以上続く箇所をグループ名fwuでグループ化
  3. (?<fwd>[0-9]+): [0-9]の範囲にある文字(全角数字)が1文字以上続く箇所をグループ名fwdでグループ化

上記のようにグループ化した上で、いずれかのグループの正規表現にマッチした文字列を、MatchEvaluatorデリゲートによってそれぞれ個別に半角に置き換えることにより、文字列中の全角英数字を半角に置き換えています。

正規表現とMatchEvaluatorを使って文字列中の全角英数文字を半角英数文字に置換する
using System;
using System.Linq;
using System.Text.RegularExpressions;

class Sample {
  static void Main()
  {
    var text = "abcxyzABCXYZ012789";

    Console.WriteLine(text);

    // 正規表現にマッチする文字列をメソッドReplaceFullWidthCharsで置換する
    Console.WriteLine(Regex.Replace(text, "(?<fwl>[a-z]+)|(?<fwu>[A-Z]+)|(?<fwd>[0-9]+)", ReplaceFullWidthChars));
  }

  static string ReplaceFullWidthChars(Match m)
  {
    if (m.Groups["fwl"].Success) {
      // 名前付きグループfwlの正規表現'a-z'にマッチした文字列を一文字(char)ずつ選択し、
      // コードポイントを'a-z'から'a-z'の範囲に移動したのち、再度文字列化したものを置換結果として返す
      return new string(m.Groups["fwl"].Value.Select(c => (char)(c - 'a' + 'a')).ToArray());
    }
    else if (m.Groups["fwu"].Success) {
      // 上記と同様にグループfwuにマッチした文字列のコードポイントを
      // 'A-Z'から'A-Z'の範囲に移動し、置換結果として返す
      return new string(m.Groups["fwu"].Value.Select(c => (char)(c - 'A' + 'A')).ToArray());
    }
    else if (m.Groups["fwd"].Success) {
      // 上記と同様にグループfwuにマッチした文字列のコードポイントを
      // '0-9'から'0-9'の範囲に移動し、置換結果として返す
      return new string(m.Groups["fwd"].Value.Select(c => (char)(c - '0' + '0')).ToArray());
    }

    return string.Empty; // (ここには到達し得ない)
  }
}
実行結果
abcxyzABCXYZ012789
abcxyzABCXYZ012789

この例の正規表現およびMatchEvaluatorを書き換えることにより、半角→全角の変換を行うように変えることも容易にできます。

この実装による全角半角変換は、正規表現を使った例として提示したものであるためパフォーマンス上の考慮を行っていません。 全角半角変換を行うことができるメソッドとしてMicrosoft.VisualBasic.Strings.StrConvが用意されていますが、System名前空間以下ではこのようなメソッドは用意されていません。