【C#】正規表現の使い方まとめ

C#正規表現の使い方をまとめます。
パターンの記述方法についてはよくまとまっている記事がいくらでもあるのでそれ以外の情報についてまとめます。

基本的な使い方

IsMatch, Match, Matches

文字列が正規表現にマッチしているかどうかを調べるにはRegex.IsMatch()を使います。

var regex = new Regex(@"^https://.+\.com");
regex.IsMatch("https://google.com"); // true

正規表現にマッチしている部分を取得するにはRegex.Match()を使います。

var regex = new Regex(@"^https://.+\.com");
var match = regex.Match("https://google.com/testtest");
match.Success; // true (マッチしたかどうか)
match.Value; // https://google.com(マッチした部分の文字列)

正規表現にマッチしている部分をいくつか取得するにはRegex.Matches()を使います。

var regex = new Regex(@"test");
var matches = regex.Matches("https://google.com/testtest");
matches.Count; // 2(マッチした個数)
matches[0].Value; // test(1個目のマッチした値)
最短マッチを表す「?」

いま、ある文字列の中から [ と ] で囲まれている部分を全て抜き出したいとします。
これを正規表現で書いてみます。

var regex = new Regex(@"\[.*\]");
var matches = regex.Matches("[test1][test2]");
matches[0].Value; // [test1][test2]

上記はうまくいくように思えますが、実際に実行してみると [test1][test2] という結果が得られます。
これは正規表現による条件を満たす部分のうち、最長の部分がマッチするという仕様によるものです。

以下のように*の後に?を付けることで、最短の部分がマッチするようになります。

var regex = new Regex(@"\[.*?\]"); // ?を付ける
var matches = regex.Matches("[test1][test2]");
matches[0].Value; // [test1]
matches[1].Value; // [test2]
オプションまとめ

正規表現クラス生成時にはオプションを指定することができます。

new Regex(@"\[.*?\]", RegexOptions.IgnoreCase | RegexOptions.Singleline);

以下、各オプションの意味についてまとめます。

名前 説明
IgnoreCase 検索時に大文字と小文字を区別しない
Multiline ^と$の意味を変更し、各行の先頭と末尾にもそれらがマッチするようにする
ExplicitCapture 名前か番号を指定したグループだけを有効なキャプチャであるとみなす
Compiled 正規表現コンパイルしてアセンブリを作成する
実行速度は速くなるが起動時間は長くなる
Singleline .の意味を変更し\nにも一致するようにする
IgnorePatternWhitespace 正規表現を複数行に分割して行末に#を使ったコメントを入れられるようになる
RightToLeft 検索が右から左になされるようになる
ECMAScript ECMAScript準拠の動作を有効にする
CultureInvariant 言語の違いが無視される
置換

Regex.Replace()を使うと正規表現にマッチした部分を置換できます。

new Regex(@"<date>")
    .Replace("Today is <date>.", DateTime.Now.ToShortDateString()); // Today is 2020/09/22.

staticメソッドも用意されています。

Regex.Replace("Today is <date>.", "<date>", DateTime.Now.ToShortDateString();
分割

Regex.Split()を使うと正規表現にマッチした部分を区切り文字として対象の文字列を分割できます。

new Regex(",").Split("98127,432,25,870,4214,234"); // ["98127", "432", "25", "870", "4214", "234"]

正規表現にグループを使うと区切り文字も含めた分割結果が得られます。

new Regex("(,)").Split("98127,432,25,870,4214,234"); // ["98127", ",", "432", ",", "25", ",", "870", ",", "4214", ",", "234"]

またstaticメソッドも用意されています。

Regex.Split("98127,432,25,870,4214,234", ",");

グループ

正規表現を()で囲むとグループを指定できます。
グループにはいくつかの使い方があるので以下にまとめます。

OR条件を作る

グループ内の文字列を | で区切ることでOR条件を指定できます。
例えば以下のように書くとSeptember、November、Decemberのいずれかであればマッチするといった条件が作れます。

var regex = new Regex(@"(Sept|Nov|Dec)ember");
var matches = regex.Matches("January February March April May June July August September November December");
matches[0].Captures[0].Value; // September
matches[1].Captures[0].Value; // November
matches[2].Captures[0].Value; // December
複数のグループを取り扱う

複数のグループを作ることもできます。
この場合にはMatch.Groupsを使って各グループの値を取り出します。

var regex = new Regex(@"(\d+)/(\d+)/(\d+)");
var match = regex.Match("2020/09/22");
match.Groups[0].Captures[0].Value; // 2020/09/22 ※Groups[0]にはマッチした部分全体の文字列が入る
match.Groups[1].Captures[0].Value; // 2020
match.Groups[2].Captures[0].Value; // 09
match.Groups[3].Captures[0].Value; // 22

またグループに ?: を付けるとそのグループはキャプチャされません。

var regex = new Regex(@"(\d+)/(?:\d+)/(\d+)"); // 二個目のグループに?:を付けた
var match = regex.Match("2020/09/22");
match.Groups[0].Captures[0].Value; // 2020/09/22
match.Groups[1].Captures[0].Value; // 2020
match.Groups[2].Captures[0].Value; // 22
名前付きグループ

グループに名前を付けることもできます。

var regex = new Regex(@"(?<year>\d+)/(?<month>\d+)/(?<day>\d+)");
var match = regex.Match("2020/09/22");
match.Groups["year"].Captures[0].Value; // 2020
match.Groups["month"].Captures[0].Value; // 09
match.Groups["day"].Captures[0].Value; // 22
前方参照コンストラク

前方参照コンストラクトを使うと以下のようにキャプチャしたグループの位置を使って正規表現を作ることができます。

var regex = new Regex(@"(\w{3})\1"); // 3文字が2回繰り返している部分にマッチ
var matches = regex.Matches("atwjamiamijqkfaqfaqfawekaekapkpewjfak");
matches[0].Value; // amiami
matches[1].Value; // faqfaq
matches[2].Value; // ekaeka

また以下のように\k<グループ名>とすれば名前付きグループを名前で指定できます。

var regex = new Regex(@"(?<source>\w{3})\k<source>");
var matches = regex.Matches("atwjamiamijqkfaqfaqfawekaekapkpewjfak");
matches[0].Value; // amiami
matches[1].Value; // faqfaq
matches[2].Value; // ekaeka
Capturesが複数になるケース

上記ではグループのCaptures[0]のみ使用していました。
以下のように一つのグループが複数回マッチするようなケースではCapturesの他のインデックスを指定する必要があります。

var regex = new Regex(@"(\d+/*)+");
var match = regex.Match("2020/09/22");
match.Groups[1].Captures[0].Value; // 2020/
match.Groups[1].Captures[1].Value; // 09/
match.Groups[1].Captures[2].Value; // 22

先読み、後読みアサーション

ゼロ幅の肯定先読みアサーション

(?= subexpression )という書式でゼロ幅の肯定先読みアサーションを使うと、
後ろに指定したパターンが現れる部分とマッチさせることができます。
またこのときsubexpressionはマッチ結果には含まれません。

var regex = new Regex(@"(?<name>\w+):(?<count>\d+)(?=個)");
var matches = regex.Matches("りんご:2個 みかん:3個 なし:5個");
$"{matches[0].Groups["name"].Value}: {matches[0].Groups["count"].Value}"; // りんご: 2
$"{matches[1].Groups["name"].Value}: {matches[1].Groups["count"].Value}"; // みかん: 3
$"{matches[2].Groups["name"].Value}: {matches[2].Groups["count"].Value}"; // なし: 5
ゼロ幅の否定先読みアサーション

(?! subexpression )という書式でゼロ幅の肯定先読みアサーションを使うと、
後ろに指定したパターンが現れない部分とマッチさせることができます。
またこのときsubexpressionはマッチ結果には含まれません。
前節の肯定先読みアサーションの反対です。

ゼロ幅の肯定後読みアサーション

(?<= subexpression )という書式でゼロ幅の肯定後読みアサーションを使うと、
前に指定したパターンが現れる部分とマッチさせることができます。
またこのときsubexpressionはマッチ結果には含まれません。

var regex = new Regex(@"(?<=-)(?<value>\d+)");
var matches = regex.Matches("-123  9028 -191 12 -1324 -158 51");
$"{matches[0].Groups["value"].Value}"; // 123
$"{matches[1].Groups["value"].Value}"; // 191
$"{matches[2].Groups["value"].Value}"; // 1324
$"{matches[3].Groups["value"].Value}"; // 158
ゼロ幅の否定後読みアサーション

(?<! subexpression )という書式でゼロ幅の否定後読みアサーションを使うと、
前に指定したパターンが現れない部分とマッチさせることができます。
またこのときsubexpressionはマッチ結果には含まれません。
前節の肯定後読みアサーションの反対です。

参考

docs.microsoft.com

docs.microsoft.com