C#でRegexクラスを活用した文字列置換の方法10選 – Japanシーモア

C#でRegexクラスを活用した文字列置換の方法10選

C# String.Regexクラスを使ったコーディングテクニックC#
この記事は約18分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

このサービスは複数のSSPによる協力の下、運営されています。

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●Regexクラスとは?

C#でテキスト処理を行うとき、文字列の検索や置換、抽出などの操作が必要になることがよくありますよね。

そんな時に力を発揮してくれるのが、.NETフレームワークに用意されているRegexクラスです。

○Regexクラスの概要

Regexクラスは、正規表現を使った高度な文字列操作を可能にしてくれる強力なツールです。

正規表現というのは、文字列のパターンを表現するための特殊な記法のことで、これを使うことで複雑な文字列マッチングや置換を柔軟に行うことができるようになります。

C#でRegexクラスを使うには、System.Text.RegularExpressionsという名前空間をusingディレクティブでインポートする必要があります。

using System.Text.RegularExpressions;

これだけで、Regexクラスのメソッドや プロパティを利用できるようになりますよ。

○正規表現の基本

Regexクラスを使いこなすには、正規表現の基本をおさえておくことが大切です。

正規表現では、特殊な意味を持つメタ文字を使ってパターンを表現します。

例えば、「.」は任意の1文字にマッチし、「*」は直前の要素を0回以上繰り返すことを意味します。

また、「[」と「]」で文字の集合を表したり、「^」で行頭、「$」で行末を表すこともできます。

正規表現を使えば、単純な文字列の一致判定だけでなく、特定の形式を満たしているかどうかのチェックや、文字列の一部を取り出すことも簡単にできるようになります。

●Regexクラスを使った文字列置換の基本

正規表現を使った文字列置換は、Regexクラスの真骨頂とも言えます。

普通の文字列置換では実現が難しい、柔軟で強力な置換処理をシンプルなコードで実装できるのが大きな魅力ですよね。

Regexクラスを使った文字列置換の基本的な流れは、次のようになります。

  1. 正規表現パターンを指定して、Regexオブジェクトを作成する
  2. RegexオブジェクトのReplaceメソッドを呼び出して、マッチした部分を置換する
  3. 置換後の文字列を取得する

それでは、具体的なサンプルコードを見ていきましょう。

シンプルな例から順を追って紹介していくので、正規表現初心者の方もきっと理解できるはずです。

○サンプルコード1:単純な文字列置換

まずは、最もシンプルな文字列置換の例から見てみましょう。

ここでは、文字列中の特定の単語を別の単語に置き換えてみます。

string text = "I have a pen. I have an apple.";
string pattern = "have";
string replacement = "had";

Regex regex = new Regex(pattern);
string result = regex.Replace(text, replacement);

Console.WriteLine(result);

実行結果

I had a pen. I had an apple.

Regexオブジェクトのコンストラクタに置換前のパターンを指定し、Replaceメソッドの第1引数に対象の文字列、第2引数に置換後の文字列を指定するだけで、簡単に置換処理が行えました。

○サンプルコード2:大文字小文字を区別しない置換

次に、大文字小文字を区別せずに置換してみましょう。

正規表現では、iオプションを指定することで大文字小文字を区別しないマッチングが可能になります。

string text = "I have a pen. I HAVE an apple.";
string pattern = "have";
string replacement = "had";

Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
string result = regex.Replace(text, replacement);

Console.WriteLine(result);

実行結果

I had a pen. I had an apple.

Regexオブジェクトのコンストラクタの第2引数にRegexOptions.IgnoreCaseを指定するだけで、大文字小文字を区別せずにマッチさせることができました。

これは、"have"という文字列を探す際に"HAVE"も対象に含めるという意味です。

○サンプルコード3:複数のパターンを置換

最後に、複数の異なるパターンを一度に置換してみましょう。

正規表現では、|を使って複数のパターンを指定することができます。

string text = "I have a pen. I have an apple. I have a pineapple.";
string pattern = "pen|apple";
string replacement = "banana";

Regex regex = new Regex(pattern);
string result = regex.Replace(text, replacement);

Console.WriteLine(result);

実行結果

I have a banana. I have an banana. I have a pinebanana.

"pen""apple"という2つのパターンを|で結合し、それらが出現する箇所をすべて"banana"に置換しました。

このように、正規表現を使えば複雑な条件でも簡潔に表現できるのが大きなメリットです。

●Regexクラスのオプション指定

Regexクラスには、正規表現のマッチング方法をカスタマイズするためのオプションがいくつか用意されています。

これらのオプションを使いこなすことで、より柔軟で強力な文字列操作が可能になります。

オプションを指定するには、Regexオブジェクトのコンストラクタの第2引数にRegexOptions列挙型の値を指定します。

複数のオプションを同時に指定したい場合は、ビット演算子の|を使って結合します。

それでは、オプションを使った具体的なサンプルコードを見ていきましょう。

○サンプルコード4:複数行モードを使った置換

文字列が複数行にわたる場合、RegexOptions.Multilineオプションを使うことで、行ごとに正規表現のマッチングを行うことができます。

例えば、各行の先頭に番号を付ける処理は次のように実装できます。

string text = "apple\nbanana\norange";
string pattern = "^";
string replacement = "${line}. ";

Regex regex = new Regex(pattern, RegexOptions.Multiline);
string result = regex.Replace(text, replacement, 
                               m => (m.Index + 1).ToString());

Console.WriteLine(result);

実行結果は次のようになります。

1. apple
2. banana
3. orange

^は、通常は文字列の先頭を表しますが、RegexOptions.Multilineオプションを指定することで、各行の先頭を表すようになります。

置換後の文字列には、ラムダ式を使ってマッチした位置に応じた連番を生成しています。

${line}は、ラムダ式の結果に置き換えられる特殊な変数です。

○サンプルコード5:文字列の先頭・末尾のマッチ

RegexOptions.Multilineオプションと併せて、RegexOptions.Singlelineオプションを使うことで、文字列の先頭や末尾とのマッチングを柔軟に行うことができます。

string text = "Hello, world!\nHow are you?";
string pattern = "^Hello.*!$";

Regex regex = new Regex(pattern, RegexOptions.Singleline);
Match match = regex.Match(text);

Console.WriteLine(match.Value);

実行結果は次のようになります。

Hello, world!

RegexOptions.Singlelineオプションを指定すると、.が改行文字にもマッチするようになります。

そのため、^$が文字列全体の先頭と末尾を表すようになります。

このオプションは、複数行にわたる文字列から特定の行を抽出したい場合などに便利です。

●グループを使った置換

正規表現でもう一つ覚えておきたい重要な機能が、グループです。

グループを使うと、正規表現のパターン内の一部分にマッチする文字列を取り出して、置換後の文字列で再利用することができます。

グループを使った置換は、単純な文字列の置換だけでは実現できない、柔軟でダイナミックな文字列操作を可能にしてくれます。

特に、文字列のフォーマットを変更する場合などに威力を発揮します。

それでは、グループを使ったサンプルコードをいくつか見ていきましょう。

○サンプルコード6:グループを使った置換

文字列中の日付の表記を、”yyyy/MM/dd”形式から”yyyy-MM-dd”形式に変更してみましょう。

string text = "今日の日付は2023/04/01です。";
string pattern = @"(\d{4})/(\d{2})/(\d{2})";
string replacement = "$1-$2-$3";

Regex regex = new Regex(pattern);
string result = regex.Replace(text, replacement);

Console.WriteLine(result);

実行結果

今日の日付は2023-04-01です。

正規表現のパターンで、()で囲んだ部分がグループとして扱われます。\d{4}は4桁の数字、\d{2}は2桁の数字にマッチします。

置換後の文字列では、$1$2$3がそれぞれ1番目、2番目、3番目のグループにマッチした文字列に置き換えられます。

このように、グループを使うことで、マッチした部分文字列を置換後の文字列で再利用できるのです。

○サンプルコード7:名前付きグループを使った置換

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

名前付きグループを使うと、置換後の文字列でグループを参照するのがより簡単になります。

string text = "鈴木太郎,suzuki@example.com,080-1234-5678";
string pattern = @"^(?<name>.+),(?<email>.+),(?<phone>.+)$";
string replacement = "${phone},${name},${email}";

Regex regex = new Regex(pattern);
string result = regex.Replace(text, replacement);

Console.WriteLine(result);

実行結果

080-1234-5678,鈴木太郎,suzuki@example.com

正規表現のパターンで、(?<name>.+)のように、グループに?<name>という形式で名前を付けています。

.+は、任意の1文字以上の文字列にマッチします。

置換後の文字列では、${name}のように、${グループ名}の形式でグループを参照しています。

これにより、グループの番号を数えなくても、わかりやすい名前でグループを参照できるようになります

●Regexクラスの応用例

ここまで、Regexクラスの基本的な使い方やオプション、グループ機能などについて見てきました。

正規表現の基礎がわかったところで、実際の開発でよく出てくる文字列処理の場面に、Regexクラスを応用してみましょう。

正規表現を使いこなせば、複雑な文字列処理もシンプルで読みやすいコードで実現できます。

ここでは、HTMLタグの除去、電話番号の抽出、URLのバリデーションという3つの具体的なシナリオを通して、Regexクラスの実践的な活用方法を解説していきます。

○サンプルコード8:HTMLタグの除去

Webスクレイピングなどで取得したHTMLから、タグを取り除いてプレーンテキストを抽出したいという場面はよくありますよね。

正規表現を使えば、こんな風に簡単に実装できます。

string html = "<p>これは<strong>サンプル</strong>の<em>HTMLです</em>。</p>";
string pattern = "<.*?>";

Regex regex = new Regex(pattern);
string result = regex.Replace(html, "");

Console.WriteLine(result);

実行結果は次のようになります。

これはサンプルのHTMLです。

正規表現のパターンで、<>の間にある任意の文字列にマッチさせています。.*?は、*?を使うことで最短マッチになります。

これにより、<p></p>の間の文字列全体ではなく、<p><strong></strong><em></em>がそれぞれ個別にマッチします。

置換後の文字列に空文字列を指定することで、マッチした部分(つまりタグ)を除去しています。

これだけでHTMLからタグを取り除くことができるのは、正規表現ならではの強みだと言えます。

○サンプルコード9:電話番号の抽出

ユーザー入力から電話番号だけを抽出したり、電話番号のフォーマットを統一したりしたい場合も、正規表現が役立ちます。

日本の電話番号を抽出する例を見てみましょう。

string text = "私の電話番号は090-1234-5678です。オフィスは03-1234-5678です。";
string pattern = @"\d{2,4}-\d{2,4}-\d{4}";

Regex regex = new Regex(pattern);
MatchCollection matches = regex.Matches(text);

foreach (Match match in matches)
{
    Console.WriteLine(match.Value);
}

実行結果

090-1234-5678
03-1234-5678

正規表現のパターンで、\d{2,4}は2桁から4桁の数字、-はハイフンにマッチします。

これにより、数字2〜4桁-数字2〜4桁-数字4桁という電話番号の一般的なフォーマットにマッチさせています。

Regex.Matchesメソッドを使うことで、マッチした部分文字列をすべて取得できます。

このように、正規表現を使えば特定の形式の文字列を柔軟に抽出することができます。

○サンプルコード10:URLのバリデーション

ユーザー入力されたURLが適切なフォーマットかどうかをチェックしたい場合も、正規表現を使うと便利です。

HTTPまたはHTTPSのURLをバリデーションする例を見てみましょう。

string[] urls = {
    "http://www.example.com",
    "https://example.com/path/to/page.html",
    "ftp://example.com",
    "http://example.com/path/to/page.html?query=string",
    "http://invalid.example.com/path/to/page.html"
};

string pattern = @"^https?://[\w-]+(\.[\w-]+)+(/[\w-./?%&=]*)?$";

Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);

foreach (string url in urls)
{
    Console.WriteLine($"{url,-45} {regex.IsMatch(url)}");
}

実行結果

http://www.example.com                        True
Example Domain
True ftp://example.com False
Example Domain
True
http://invalid.example.com/path/to/page.html
False

正規表現のパターンは少し複雑ですが、^は文字列の先頭、https?はhttpまたはhttps、://://[\w-]+(\.[\w-]+)+はドメイン名、(/[\w-./?%&=]*)?はパスとクエリ文字列にマッチします。

Regex.IsMatchメソッドを使うと、文字列が正規表現パターンにマッチするかどうかを簡単にチェックできます。

このように、正規表現を使えば複雑な文字列のバリデーションも簡潔に記述できます。

●よくあるエラーと対処法

正規表現を使った文字列処理は非常に強力ですが、時にはうまく動作しないことがあります。

正規表現初心者の方なら、一度は「あれ?思った通りにマッチしない…」と頭を抱えた経験があるのではないでしょうか。

ここでは、Regexクラスを使っていて遭遇しがちなエラーとその対処法を3つ紹介します。

これらのポイントを押さえておけば、正規表現でつまずいた時にもスムーズに問題を解決できるはずです。

○パターンが不正な場合

正規表現のパターンを記述する際に、誤った文法を使ってしまうことがあります。

例えば、[]の対応が取れていなかったり、()の数が合っていなかったりすると、ArgumentExceptionが発生します。

string pattern = "[a-z";  // 閉じる `]` がない

Regex regex = new Regex(pattern);  // ここで例外が発生

このようなエラーを防ぐには、正規表現の文法をしっかりと理解することが大切です。

パターンを書いた後は、意図通りにマッチするかどうかを必ず確認しましょう。

また、正規表現のパターンを動的に生成する場合は、Regex.Escapeメソッドを使って特殊文字をエスケープするのも有効です。

string input = "(abc)";
string escaped = Regex.Escape(input);  // @"\(abc\)" に変換される

○置換後の文字列が意図しない結果になる場合

Regex.Replaceメソッドを使った置換処理で、意図しない結果になることがあります。

例えば、置換後の文字列に$\などの特殊文字が含まれていると、予期せぬ動作をすることがあります。

string text = "Hello, world!";
string pattern = "world";
string replacement = "$&";  // マッチした文字列全体を表す特殊変数

string result = Regex.Replace(text, pattern, replacement);
Console.WriteLine(result);  // "Hello, world!" になってしまう

このようなエラーを防ぐには、置換後の文字列をリテラルとして扱うために、Regex.Escapeメソッドを使って特殊文字をエスケープします。

string replacement = Regex.Escape("$&");

string result = Regex.Replace(text, pattern, replacement);
Console.WriteLine(result);  // "Hello, $&!"

○パフォーマンスが低下する場合

正規表現は非常に強力な機能ですが、複雑なパターンを使いすぎるとパフォーマンスが低下することがあります。

特に、.*などの貪欲なマッチングを多用すると、マッチングに時間がかかってしまいます。

string text = File.ReadAllText("very_large_file.txt");
string pattern = @"^.*important_keyword.*$";

Regex regex = new Regex(pattern);
MatchCollection matches = regex.Matches(text);  // とても時間がかかる

このようなパフォーマンスの問題を避けるには、正規表現のパターンをできるだけシンプルにすることが大切です。

また、RegexOptions.Compiledオプションを使って正規表現をコンパイルするのも効果的です。

string pattern = @"important_keyword";

Regex regex = new Regex(pattern, RegexOptions.Compiled);
MatchCollection matches = regex.Matches(text);  // 高速になる

RegexOptions.Compiledを使うと、正規表現のパターンがコンパイルされるため、同じパターンで繰り返しマッチングを行う場合に効果を発揮します。

まとめ

C#のRegexクラスを使った文字列置換の方法について、基本的な使い方からオプション、グループ、そして実践的な応用例まで幅広く解説してきました。

正規表現は一見難しく感じるかもしれませんが、Regexクラスを活用することで、非常に強力で柔軟な文字列操作が可能になります。

本記事で紹介したサンプルコードを参考に、実際のプロジェクトでRegexクラスを活用してみてください。正規表現のスキルを磨くことで、C#での文字列処理の幅が大きく広がるはずです。

また、チームメンバーとの知識共有にも役立てていただければ幸いです。

最後までお読みいただき、ありがとうございました。

Regexクラスを使いこなして、C#でのテキスト処理のエキスパートを目指しましょう!