クラス

【C#】正規表現で数字とハイフンのみを安全に判定する方法と活用例

C#で数字とハイフンだけの文字列を判定する場合、先頭と末尾を固定した正規表現^[0-9-]+$を使えば、意図しない英字や記号を排除できるため、郵便番号や管理コードなどのチェックを簡潔かつ安全に行えます。

正規表現の基礎知識

プログラミングにおいて、文字列のパターンマッチングは非常に重要な役割を果たします。

特に入力値の検証やデータの抽出、置換などで活用されるのが「正規表現」です。

ここでは、正規表現がなぜ選ばれるのか、そしてC#での利用に欠かせないRegexクラスについて詳しく解説します。

正規表現が選ばれる理由

正規表現は、文字列のパターンを簡潔に表現できる強力なツールです。

以下のような理由で多くの開発現場で利用されています。

  • 柔軟性が高い

数字や文字、記号の組み合わせを細かく指定できるため、複雑なパターンも一つの式で表現可能です。

例えば、電話番号やメールアドレス、郵便番号などの形式チェックに適しています。

  • 簡潔に記述できる

複雑な条件を長いコードで書く代わりに、正規表現なら短いパターンで済みます。

これによりコードの見通しが良くなり、保守性も向上します。

  • 高速なマッチング

多くのプログラミング言語で最適化された正規表現エンジンが組み込まれており、大量の文字列処理でも効率的に動作します。

  • 標準化されている

多くの言語やツールで共通の正規表現構文が使われているため、習得すれば他の環境でも応用しやすいです。

これらの理由から、数字とハイフンのみを判定するような単純なケースでも、正規表現は非常に便利な選択肢となります。

C#のRegexクラス概要

C#では、正規表現を扱うためにSystem.Text.RegularExpressions名前空間に含まれるRegexクラスが用意されています。

このクラスは、正規表現のパターンを指定して文字列の検索や置換、検証を行うための機能を提供しています。

Regexクラスは、以下のような特徴を持っています。

  • 静的メソッドとインスタンスメソッドの両方を提供

簡単なパターンマッチングなら静的メソッドRegex.IsMatchを使い、複数回同じパターンを使う場合はインスタンスを生成してパフォーマンスを向上させることができます。

  • 豊富なオプション設定

大文字・小文字の区別やマルチラインモードなど、細かい動作を制御できます。

  • 例外処理対応

無効な正規表現パターンを指定した場合は例外が発生するため、適切にハンドリングが可能です。

主なメソッド一覧

Regexクラスでよく使われるメソッドを以下にまとめます。

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

これらのメソッドを使い分けることで、文字列の検証や加工を柔軟に行えます。

オプション設定

Regexクラスのコンストラクタや静的メソッドには、動作を制御するためのオプションを指定できます。

主なオプションは以下の通りです。

オプション名説明
RegexOptions.IgnoreCase大文字・小文字を区別しないマッチングを行います。
RegexOptions.Multiline^$ が行の先頭・末尾にもマッチするようになります。
RegexOptions.Compiled正規表現をコンパイルして高速化します。
RegexOptions.CultureInvariant文化依存しない比較を行います。
RegexOptions.Singleline. が改行文字にもマッチするようになります。

例えば、数字とハイフンのみを判定する場合は大文字・小文字の区別は不要ですが、複雑なパターンで文字種を区別したくない場合にIgnoreCaseを使うことがあります。

これらのオプションを適切に設定することで、正規表現の動作を目的に合わせて最適化できます。

以上が正規表現の基礎知識とC#のRegexクラスの概要です。

パターン作成のポイント

正規表現で数字とハイフンのみを判定する際には、パターンの書き方にいくつか注意すべきポイントがあります。

ここでは、数字の指定方法、ハイフンの扱い方、そして文字列全体にマッチさせるためのアンカーの使い方について詳しく説明します。

数字 [0-9] の指定方法

数字を表す正規表現の基本は、文字クラス[0-9]です。

これは「0から9までのいずれかの数字1文字」を意味します。

例えば、[0-9]は「0」「1」「2」…「9」のいずれかにマッチします。

数字を複数回繰り返す場合は、量指定子を使います。

例えば、[0-9]+は「1文字以上の数字の連続」にマッチします。

+は「1回以上の繰り返し」を意味します。

また、[0-9]\dと書くこともできます。

\dは「数字1文字」を表すエスケープシーケンスです。

ただし、\dはUnicodeの数字全般にマッチするため、厳密に0~9の半角数字だけを対象にしたい場合は[0-9]の方が安全です。

  • [0-9]{3}:3桁の数字にマッチ
  • \d{4,6}:4~6桁の数字にマッチ(ただしUnicode数字も含む)

数字だけを判定したい場合は、[0-9]を使うのが一般的です。

ハイフン – の扱いと注意点

ハイフンは正規表現の文字クラス内で特別な意味を持ちます。

文字クラス[]の中でハイフンは「範囲指定」を表すため、例えば[a-z]は「aからzまでのすべての小文字アルファベット」を意味します。

そのため、数字とハイフンを同時に指定する場合、ハイフンの位置やエスケープに注意が必要です。

文字クラス内の位置とエスケープ

ハイフンを文字として扱いたい場合、以下のいずれかの方法で指定します。

  1. 文字クラスの先頭または末尾に置く

例:[-0-9] または [0-9-]

これによりハイフンは範囲指定ではなく、単なる文字として認識されます。

  1. バックスラッシュでエスケープする

例:[0-9\-]

\-と書くことでハイフンを文字として扱います。

どちらの方法でも問題ありませんが、先頭や末尾に置く方法の方が読みやすく、一般的に推奨されます。

誤った例:

  • [0-9-9]:これは範囲指定として解釈され、意図しない動作になる可能性があります

ハイフンを含む文字クラスは、必ず範囲指定と誤解されないように書くことが重要です。

アンカー ^ と $ で全体一致

正規表現で文字列全体が条件に合致しているかを判定したい場合、アンカーを使います。

  • ^ は文字列の先頭を示します
  • $ は文字列の末尾を示します

これらをパターンの前後に付けることで、「文字列全体がパターンにマッチする」ことを保証します。

例えば、^[0-9-]+$は「文字列の先頭から末尾まで、数字かハイフンが1文字以上連続している」ことを意味します。

アンカーを使わない場合、部分一致となり、文字列の一部に条件を満たす部分があればマッチと判定されてしまいます。

部分一致との違い

部分一致は、文字列のどこかにパターンが含まれていればマッチと判定されます。

例えば、パターン[0-9-]+だけを使うと、文字列abc123-xyzの中の123-にマッチします。

一方、^[0-9-]+$を使うと、abc123-xyzはマッチしません。

なぜなら、文字列全体が数字とハイフンだけで構成されていないからです。

この違いは入力検証の際に非常に重要です。

数字とハイフンのみの文字列を判定したい場合は、必ずアンカーを使って全体一致を指定してください。

これらのポイントを踏まえて、数字とハイフンのみを安全に判定する正規表現パターンを作成すると、^[0-9-]+$のようなシンプルで正確なパターンが完成します。

基本パターン

数字とハイフンのみを判定する正規表現の基本形は、^[0-9-]+$です。

ここではこのパターンの意味を詳しく解説し、さらに実際の利用シーンに応じたバリエーションを紹介します。

^[0-9-]+$ の意味

この正規表現は以下の要素で構成されています。

  • ^:文字列の先頭を示します
  • [0-9-]:数字(0~9)またはハイフン(-)のいずれか1文字にマッチします
  • +:直前の文字クラスが1回以上繰り返されることを示します
  • $:文字列の末尾を示します

つまり、文字列全体が「数字かハイフンのいずれかの文字で構成されている」ことを意味します。

空文字列はマッチしません。

このパターンは、例えば「123-456」「987654」「12-34-56」など、数字とハイフンだけで構成された文字列を判定するのに適しています。

バリエーション

実際の用途によっては、単に数字とハイフンが混在しているだけでなく、より厳密なルールを設けたい場合があります。

ここでは代表的なバリエーションを紹介します。

連続ハイフンを避ける

連続したハイフン--を許さないパターンです。

連続ハイフンは誤入力やフォーマット違反の原因になることが多いため、これを禁止したいケースがあります。

連続ハイフンを避けるには、以下のようなパターンが考えられます。

^(?:[0-9]+(?:-[0-9]+)*)$ 解説:

  • ^$:文字列全体の先頭と末尾を示します
  • (?: ... ):グループ化(キャプチャしない)
  • [0-9]+:1回以上の数字
  • (?:-[0-9]+)*:ハイフンと数字の組み合わせが0回以上繰り返される

このパターンは、数字の塊がハイフンで区切られている形式を表し、連続ハイフンはマッチしません。

  • マッチする文字列:123-456-789
  • マッチしない文字列:123--456

先頭末尾のハイフン禁止

文字列の先頭や末尾にハイフンがあると不正なフォーマットとみなす場合があります。

これを禁止するパターンは以下のようになります。

^[0-9]+(?:-[0-9]+)*$ 解説:

  • 先頭は必ず数字1文字以上
  • その後、ハイフンと数字の組み合わせが0回以上続く
  • 末尾は数字で終わるため、ハイフンで終わらない

このパターンは、先頭と末尾にハイフンがないことを保証します。

  • マッチする文字列:123-456
  • マッチしない文字列:-123-456123-456-

桁数制限付きパターン

特定の桁数や長さの制限を設けたい場合もあります。

例えば、数字とハイフンを含む文字列の長さを5~10文字に制限したい場合は、以下のように書けます。

^[0-9-]{5,10}$ 解説:

  • {5,10}:直前の文字クラスが5回以上10回以下繰り返される
  • 先頭と末尾のアンカーで全体一致を保証

ただし、このパターンはハイフンの位置や連続を制御しないため、必要に応じて他の条件と組み合わせて使うことが多いです。

また、数字の桁数だけを制限したい場合は、より複雑なパターンが必要です。

例えば、ハイフンを含めずに数字が合計で8桁であることを保証するのは正規表現だけでは難しいため、プログラム側での追加チェックが推奨されます。

これらの基本パターンとバリエーションを組み合わせることで、用途に応じた柔軟な数字とハイフンの判定が可能になります。

実装パターン

C#で数字とハイフンのみを判定する際、RegexクラスのIsMatchメソッドを使った入力検証が基本となります。

ここでは、コンソールアプリケーションでの具体的な実装例と例外処理の扱い方、さらにパフォーマンス向上のためのRegexOptions.Compiledオプションの利用方法とその効果について説明します。

Regex.IsMatch を用いた入力検証

Regex.IsMatchは、指定した文字列が正規表現パターンにマッチするかどうかを真偽値で返すメソッドです。

入力検証に最適で、シンプルかつ直感的に使えます。

コンソールアプリ例

以下は、数字とハイフンのみで構成された文字列かどうかを判定するコンソールアプリの例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        // 判定対象の文字列配列
        string[] testInputs = { "123-456", "7890", "12--34", "abc-123", "-12345", "12345-" };
        // 数字とハイフンのみを許可する正規表現パターン
        string pattern = @"^[0-9-]+$";
        foreach (var input in testInputs)
        {
            bool isValid = Regex.IsMatch(input, pattern);
            Console.WriteLine($"入力: '{input}' は数字とハイフンのみで構成されているか? → {isValid}");
        }
    }
}
入力: '123-456' は数字とハイフンのみで構成されているか? → True
入力: '7890' は数字とハイフンのみで構成されているか? → True
入力: '12--34' は数字とハイフンのみで構成されているか? → True
入力: 'abc-123' は数字とハイフンのみで構成されているか? → False
入力: '-12345' は数字とハイフンのみで構成されているか? → True
入力: '12345-' は数字とハイフンのみで構成されているか? → True

この例では、単純に数字とハイフンの文字だけで構成されているかを判定しています。

連続ハイフンや先頭・末尾のハイフンは許容されているため、必要に応じてパターンを調整してください。

例外処理の扱い

正規表現のパターンが不正な場合、Regex.IsMatchArgumentExceptionをスローします。

実際のアプリケーションでは、ユーザー入力や外部からのパターン指定がある場合に備えて例外処理を行うことが望ましいです。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^[0-9-]+$";
        string input = "123-456";
        try
        {
            bool isValid = Regex.IsMatch(input, pattern);
            Console.WriteLine($"入力 '{input}' の検証結果: {isValid}");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"正規表現パターンが不正です: {ex.Message}");
        }
    }
}
入力 '123-456' の検証結果: True

このように、例外をキャッチして適切にエラーメッセージを表示することで、予期しないクラッシュを防げます。

RegexOptions.Compiled の利用

RegexOptions.Compiledは、正規表現パターンをILコードにコンパイルして高速化を図るオプションです。

大量のマッチング処理を繰り返す場合に効果的です。

パフォーマンス計測

以下のコードは、RegexOptions.Compiledを使った場合と使わない場合のパフォーマンスを比較する例です。

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^[0-9-]+$";
        string input = "123-456-7890";
        int iterations = 1000000;
        // 通常のRegexインスタンス
        Regex regexNormal = new Regex(pattern);
        // Compiledオプション付きRegexインスタンス
        Regex regexCompiled = new Regex(pattern, RegexOptions.Compiled);
        Stopwatch sw = new Stopwatch();
        // 通常のRegexで計測
        sw.Start();
        for (int i = 0; i < iterations; i++)
        {
            regexNormal.IsMatch(input);
        }
        sw.Stop();
        Console.WriteLine($"通常のRegex: {sw.ElapsedMilliseconds} ms");
        // Compiledオプションで計測
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            regexCompiled.IsMatch(input);
        }
        sw.Stop();
        Console.WriteLine($"Compiledオプション付きRegex: {sw.ElapsedMilliseconds} ms");
    }
}
通常のRegex: 62 ms
Compiledオプション付きRegex: 53 ms

(※実行環境によって数値は異なります)

この結果から、RegexOptions.Compiledを使うことでパフォーマンスが向上することがわかります。

ただし、コンパイル時に初期コストがかかるため、短時間で一度だけ使う場合は効果が薄いことに注意してください。

これらの実装パターンを理解し、用途に応じて使い分けることで、数字とハイフンのみの入力検証を効率的かつ安全に行えます。

実用シナリオ

数字とハイフンのみを判定する正規表現は、さまざまな実務で役立ちます。

ここでは、具体的な利用例として郵便番号の検証、資産管理コードの検証、そして日付や識別子への応用を取り上げます。

郵便番号の検証

日本の郵便番号は一般的に「3桁-4桁」の形式で表されます。

例えば「123-4567」のような形です。

この形式を正規表現で検証する場合、数字とハイフンの組み合わせを正確に指定する必要があります。

以下のパターンは郵便番号の形式を検証する例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string[] postalCodes = { "123-4567", "1234567", "12-34567", "123-456", "abc-defg" };
        string pattern = @"^\d{3}-\d{4}$"; // 3桁の数字 + ハイフン + 4桁の数字
        foreach (var code in postalCodes)
        {
            bool isValid = Regex.IsMatch(code, pattern);
            Console.WriteLine($"郵便番号 '{code}' の検証結果: {isValid}");
        }
    }
}
郵便番号 '123-4567' の検証結果: True
郵便番号 '1234567' の検証結果: False
郵便番号 '12-34567' の検証結果: False
郵便番号 '123-456' の検証結果: False
郵便番号 'abc-defg' の検証結果: False

この例では、^\d{3}-\d{4}$というパターンを使い、3桁の数字、ハイフン、4桁の数字の順で構成されているかを厳密にチェックしています。

数字とハイフンのみの判定に加え、桁数や位置も制御できるため、郵便番号の検証に最適です。

資産管理コードの検証

企業や組織で使われる資産管理コードは、数字とハイフンを組み合わせた形式が多く見られます。

例えば「123-456-7890」のように複数の数字の塊をハイフンで区切るケースです。

このようなコードの検証には、連続したハイフンを避け、先頭と末尾にハイフンがないことを保証するパターンが適しています。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string[] assetCodes = { "123-456-7890", "123--456", "-123-456", "123-456-", "1234567890" };
        string pattern = @"^[0-9]+(?:-[0-9]+)*$";
        foreach (var code in assetCodes)
        {
            bool isValid = Regex.IsMatch(code, pattern);
            Console.WriteLine($"資産管理コード '{code}' の検証結果: {isValid}");
        }
    }
}
資産管理コード '123-456-7890' の検証結果: True
資産管理コード '123--456' の検証結果: False
資産管理コード '-123-456' の検証結果: False
資産管理コード '123-456-' の検証結果: False
資産管理コード '1234567890' の検証結果: True

このパターンは、数字の塊がハイフンで区切られている形式を表し、連続ハイフンや先頭・末尾のハイフンを禁止しています。

資産管理コードのフォーマットチェックに役立ちます。

日付や識別子への応用

数字とハイフンの組み合わせは、日付や識別子のフォーマットにもよく使われます。

例えば、ISO形式の日付「2024-06-15」や、特定の識別子「123-4567-8901」などです。

日付の検証では、単に数字とハイフンの並びをチェックするだけでなく、年月日として妥当かどうかの判定も必要ですが、まずは形式の検証に正規表現を使うことが多いです。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string[] dates = { "2024-06-15", "2024-6-15", "2024/06/15", "20240615", "2024-13-01" };
        string pattern = @"^\d{4}-\d{2}-\d{2}$"; // YYYY-MM-DD形式の基本チェック
        foreach (var date in dates)
        {
            bool isValid = Regex.IsMatch(date, pattern);
            Console.WriteLine($"日付 '{date}' の形式検証結果: {isValid}");
        }
    }
}
日付 '2024-06-15' の形式検証結果: True
日付 '2024-6-15' の形式検証結果: False
日付 '2024/06/15' の形式検証結果: False
日付 '20240615' の形式検証結果: False
日付 '2024-13-01' の形式検証結果: True

この例では、4桁の数字、ハイフン、2桁の数字、ハイフン、2桁の数字という形式をチェックしています。

月や日の妥当性までは判定していませんが、形式の誤りは検出できます。

識別子の場合も同様に、数字とハイフンの組み合わせで構成されるパターンを正規表現で検証し、フォーマットの一貫性を保つことが可能です。

これらの実用シナリオでは、数字とハイフンのみを安全に判定する正規表現が役立ちます。

用途に応じてパターンを調整し、入力の正確性を高めることができます。

フロントエンド連携

数字とハイフンのみを判定する正規表現は、C#のフロントエンド技術と組み合わせてユーザー入力の検証に活用できます。

ここでは、WinFormsのMaskedTextBox、WPFのValidationRule、そしてASP.NET CoreのDataAnnotationsを使った連携方法を具体的に説明します。

WinFormsのMaskedTextBox

MaskedTextBoxは、WinFormsで入力マスクを設定できるテキストボックスコントロールです。

数字やハイフンなど特定の文字種を制限しながら入力を受け付けることができます。

数字とハイフンのみを許可する場合、マスクを以下のように設定します。

using System;
using System.Windows.Forms;
class Program : Form
{
    private MaskedTextBox maskedTextBox;
    public Program()
    {
        maskedTextBox = new MaskedTextBox
        {
            Mask = "000-000-0000", // 数字3桁-数字3桁-数字4桁の例
            PromptChar = '_',
            Location = new System.Drawing.Point(10, 10),
            Width = 150
        };
        Controls.Add(maskedTextBox);
        Button validateButton = new Button
        {
            Text = "検証",
            Location = new System.Drawing.Point(10, 40)
        };
        validateButton.Click += ValidateButton_Click;
        Controls.Add(validateButton);
    }
    private void ValidateButton_Click(object sender, EventArgs e)
    {
        string input = maskedTextBox.Text;
        // ハイフンと数字のみかを正規表現でチェック
        bool isValid = System.Text.RegularExpressions.Regex.IsMatch(input, @"^[0-9-]+$");
        MessageBox.Show(isValid ? "入力は有効です" : "入力に誤りがあります");
    }
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Program());
    }
}

この例では、Maskプロパティで「数字3桁-数字3桁-数字4桁」の形式を指定しています。

ユーザーは数字とハイフンの位置に従って入力でき、ボタンを押すと正規表現でさらに検証します。

MaskedTextBoxは入力時点での制限が可能なため、誤入力を減らせます。

WPFのValidationRule

WPFでは、ValidationRuleを使ってバインディング時に入力値の検証を行えます。

数字とハイフンのみを許可するカスタムバリデーションルールを作成し、テキストボックスに適用する例を示します。

using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
public class NumericHyphenValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        string input = value as string ?? string.Empty;
        string pattern = @"^[0-9-]+$";
        if (Regex.IsMatch(input, pattern))
        {
            return ValidationResult.ValidResult;
        }
        else
        {
            return new ValidationResult(false, "数字とハイフンのみを入力してください");
        }
    }
}

XAMLでの適用例:

<Window x:Class="SampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:SampleApp"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Validation Sample" Height="150" Width="300">
    <Grid Margin="10">
        <TextBox>
            <TextBox.Text>
                <Binding Path="InputText" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <local:NumericHyphenValidationRule />
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
    </Grid>
</Window>

この方法で、ユーザーが数字とハイフン以外の文字を入力すると、バリデーションエラーが発生し、UIにエラーメッセージを表示できます。

ValidationRuleはMVVMパターンと相性が良く、入力検証をViewModelに依存させずに実装可能です。

ASP.NET CoreのDataAnnotations

ASP.NET Coreのモデルバインディングでは、DataAnnotations属性を使って入力検証を行います。

数字とハイフンのみを許可するカスタム属性を作成し、モデルのプロパティに適用する例です。

using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
public class NumericHyphenAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string input = value as string ?? string.Empty;
        string pattern = @"^[0-9-]+$";
        if (Regex.IsMatch(input, pattern))
        {
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult("数字とハイフンのみを入力してください");
        }
    }
}
public class SampleModel
{
    [NumericHyphen]
    public string Code { get; set; }
}

コントローラーのアクション例:

using Microsoft.AspNetCore.Mvc;
public class SampleController : Controller
{
    [HttpPost]
    public IActionResult Submit(SampleModel model)
    {
        if (ModelState.IsValid)
        {
            return Content("入力は有効です");
        }
        else
        {
            return BadRequest(ModelState);
        }
    }
}

このように、DataAnnotationsを使うとサーバーサイドでの入力検証が簡単に実装でき、クライアント側のJavaScriptバリデーションとも連携可能です。

正規表現を用いたカスタム属性で柔軟に検証ルールを定義できます。

これらのフロントエンド連携手法を活用することで、数字とハイフンのみを安全に判定しつつ、ユーザー体験を向上させることができます。

用途や環境に応じて適切な方法を選択してください。

ユニットテスト

正規表現を使った数字とハイフンのみの判定は、正確性を保つためにユニットテストで検証することが重要です。

ここでは、C#の代表的なテストフレームワークであるxUnitを使い、正常系と異常系のテストケース設計と実装例を示します。

xUnitによるケース設計

xUnitはシンプルで拡張性の高いテストフレームワークで、.NET環境で広く使われています。

テストメソッドには[Fact][Theory]属性を使い、パラメータ化テストも容易に行えます。

数字とハイフンのみを判定するメソッドをテストする場合、以下のようにケースを分けて設計します。

  • 正常系:数字とハイフンのみで構成された文字列が正しくマッチすること
  • 異常系:数字以外の文字や不正なハイフンの使い方がマッチしないこと

正常系テスト

正常系では、期待通りにマッチする文字列を複数用意し、すべてがtrueを返すことを確認します。

using System.Text.RegularExpressions;
using Xunit;
public class NumericHyphenValidationTests
{
    private const string Pattern = @"^[0-9-]+$";
    [Theory]
    [InlineData("123456")]
    [InlineData("123-456")]
    [InlineData("0-9-0-9")]
    [InlineData("9999999999")]
    [InlineData("1-2-3-4-5")]
    public void IsMatch_ValidInputs_ReturnsTrue(string input)
    {
        bool result = Regex.IsMatch(input, Pattern);
        Assert.True(result);
    }
}

このテストでは、[Theory][InlineData]を使って複数の正常な入力を一括で検証しています。

すべてのケースでRegex.IsMatchtrueを返すことを期待しています。

異常系テスト

異常系では、数字とハイフン以外の文字が含まれるケースや、空文字、空白文字、連続ハイフンなど不正なパターンを検証し、falseが返ることを確認します。

using System.Text.RegularExpressions;
using Xunit;
public class NumericHyphenValidationTests
{
    private const string Pattern = @"^[0-9-]+$";
    [Theory]
    [InlineData("abc123")]
    [InlineData("123_456")]
    [InlineData("12--34")]
    [InlineData("")]
    [InlineData(" ")]
    [InlineData("123 456")]
    [InlineData("-123-")]
    public void IsMatch_InvalidInputs_ReturnsFalse(string input)
    {
        bool result = Regex.IsMatch(input, Pattern);
        Assert.False(result);
    }
}

このテストでは、数字とハイフン以外の文字や空文字、空白、連続ハイフン、先頭・末尾のハイフンなどを含む文字列を検証しています。

パターンによっては連続ハイフンや先頭末尾のハイフンを許容する場合もありますが、ここでは基本的な^[0-9-]+$パターンを想定しています。

これらのユニットテストを実行することで、正規表現の判定ロジックが期待通りに動作しているかを自動的に検証できます。

テストケースは必要に応じて拡充し、仕様変更時のリグレッション防止にも役立ててください。

メンテナンス性向上

正規表現は強力ですが、複雑になると可読性が低下し、保守が難しくなります。

ここでは、コメント付き正規表現による可読性向上の方法と、パターンの再利用・共通化のためのstatic readonlyフィールドの活用について解説します。

コメント付き正規表現

正規表現は一見すると記号の羅列でわかりにくいため、コメントを付けて意味を明示することが重要です。

C#のRegexOptions.IgnorePatternWhitespaceオプションを使うと、空白や改行を無視しつつ、コメントを挿入できる「コメント付き正規表現」が利用可能です。

可読性を高める書式

コメント付き正規表現では、パターン内に#以降の文字列をコメントとして記述できます。

また、空白や改行は無視されるため、複雑なパターンを複数行に分けて整理できます。

例として、数字とハイフンのみを許可するパターンをコメント付きで書くと以下のようになります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"
            ^               # 文字列の先頭
            [0-9-]+         # 数字またはハイフンが1回以上
            $               # 文字列の末尾
        ";
        string input = "123-456-7890";
        bool isMatch = Regex.IsMatch(input, pattern, RegexOptions.IgnorePatternWhitespace);
        Console.WriteLine($"入力 '{input}' はパターンにマッチしますか? → {isMatch}");
    }
}
入力 '123-456-7890' はパターンにマッチしますか? → True

このように、パターンを複数行に分けてコメントを入れることで、何を意図しているのかが明確になり、後から見たときの理解が容易になります。

注意点として、RegexOptions.IgnorePatternWhitespaceを指定しないと、空白や改行もパターンの一部として扱われてしまうため、必ずオプションを付けて使います。

パターンの再利用と共通化

正規表現パターンは複数箇所で使い回すことが多いため、コード内で共通化して管理するとメンテナンスが楽になります。

特に大規模なプロジェクトでは、パターンの変更があった際に一箇所だけ修正すれば済むようにすることが重要です。

static readonly フィールド

C#では、正規表現パターンやRegexオブジェクトをstatic readonlyフィールドとして定義し、使い回す方法が一般的です。

これにより、パターンの重複を防ぎ、パフォーマンスも向上します。

using System;
using System.Text.RegularExpressions;
class Validator
{
    // 数字とハイフンのみを許可する正規表現パターン(コメント付きは省略)
    private static readonly string NumericHyphenPattern = @"^[0-9-]+$";
    // Regexオブジェクトをstatic readonlyで生成(コンパイルオプション付き)
    private static readonly Regex NumericHyphenRegex = new Regex(
        NumericHyphenPattern,
        RegexOptions.Compiled | RegexOptions.CultureInvariant
    );
    public static bool IsValid(string input)
    {
        if (string.IsNullOrEmpty(input))
            return false;
        return NumericHyphenRegex.IsMatch(input);
    }
}
class Program
{
    static void Main()
    {
        string[] inputs = { "123-456", "abc-123", "123--456" };
        foreach (var input in inputs)
        {
            bool isValid = Validator.IsValid(input);
            Console.WriteLine($"入力 '{input}' の検証結果: {isValid}");
        }
    }
}
入力 '123-456' の検証結果: True
入力 'abc-123' の検証結果: False
入力 '123--456' の検証結果: True

この例では、Validatorクラス内にパターンとRegexオブジェクトをstatic readonlyで定義しています。

RegexOptions.Compiledを付けることでパフォーマンスも向上し、複数回の呼び出しで効率的にマッチングが行えます。

このように、コメント付き正規表現で可読性を高め、static readonlyフィールドでパターンを共通化することで、保守性の高いコードを実現できます。

正規表現は強力ですが複雑になりやすいため、これらの工夫を取り入れることをおすすめします。

セキュリティと信頼性

正規表現を用いた入力検証は便利ですが、セキュリティ面や信頼性を考慮しないと問題が発生することがあります。

ここでは、正規表現に潜むReDoS(正規表現によるサービス拒否攻撃)のリスクとその対策、さらに入力データのサニタイズにおけるホワイトリスト方式について解説します。

ReDoSのリスク

ReDoS(Regular Expression Denial of Service)は、悪意のある入力によって正規表現の処理時間が異常に長くなり、サービスが停止または遅延する攻撃手法です。

特に複雑な正規表現やバックトラッキングが多発するパターンで発生しやすいです。

数字とハイフンのみを判定する単純な正規表現(例:^[0-9-]+$)は比較的安全ですが、複雑なパターンや繰り返しの多いパターンを組み合わせる場合は注意が必要です。

複雑度を抑える工夫

ReDoSのリスクを低減するためには、以下のポイントを意識して正規表現を設計します。

  • 過剰なネストや繰り返しを避ける

例えば、(a+)+のようなネストした繰り返しはバックトラッキングが爆発的に増え、処理時間が急増します。

数字とハイフンの判定では、単純な文字クラスと量指定子を使い、ネストを避けることが重要です。

  • 具体的な長さ制限を設ける

{min,max}の量指定子で繰り返し回数を制限し、極端に長い入力を防ぎます。

例えば、^[0-9-]{1,20}$のように最大長を決めると処理負荷を抑えられます。

  • RegexOptions.Compiledの利用

コンパイル済み正規表現はパフォーマンスが向上し、処理時間のばらつきを減らせます。

  • 入力長の事前チェック

正規表現を実行する前に、入力文字列の長さを制限することで、極端に長い文字列による負荷を防止します。

  • 正規表現のテストツールを活用

ReDoS脆弱性を検出するツールやオンラインサービスを使い、パターンの安全性を検証します。

これらの工夫により、ReDoS攻撃のリスクを大幅に減らせます。

入力データのサニタイズ

入力データのサニタイズは、外部からの入力を安全に処理するための重要な対策です。

正規表現による検証と組み合わせて、不要な文字や危険な文字を除去または拒否することで、システムの信頼性を高めます。

ホワイトリスト方式

ホワイトリスト方式は、許可する文字やパターンを明確に定義し、それ以外の入力を拒否する方法です。

数字とハイフンのみを許可する場合は、まさにホワイトリスト方式の典型例です。

具体的には、正規表現で^[0-9-]+$のように「数字とハイフン以外は受け付けない」ルールを設けます。

これにより、SQLインジェクションやクロスサイトスクリプティング(XSS)などの攻撃に使われる特殊文字の混入を防げます。

ホワイトリスト方式のメリット:

  • 明確な許可基準

許可する文字種が限定されているため、予期しない入力を排除しやすい。

  • セキュリティ強化

不正な文字やコードの混入を防ぎ、攻撃リスクを低減。

  • 検証がシンプル

許可文字だけを列挙するため、検証ロジックがわかりやすい。

一方で、ホワイトリストに含まれない正当な入力を誤って拒否するリスクもあるため、許可文字の選定は慎重に行う必要があります。

これらのセキュリティ対策を踏まえ、正規表現を用いた数字とハイフンの判定を安全かつ信頼性の高いものにしてください。

特に外部入力を扱う場合は、ReDoSのリスクを意識しつつ、ホワイトリスト方式で厳格に検証することが重要です。

パフォーマンス最適化

正規表現を使った文字列判定は便利ですが、処理回数が多い場合や大量データを扱う場合はパフォーマンスに注意が必要です。

ここでは、C#での正規表現のパフォーマンスを向上させるための「キャッシュとプーリング」の活用方法と、「非同期処理との相性」について解説します。

キャッシュとプーリング

正規表現のパターンは、毎回新しくコンパイルすると処理コストが高くなります。

特に同じパターンを何度も使う場合は、Regexオブジェクトを再利用することでパフォーマンスを大幅に改善できます。

Regexオブジェクトのキャッシュ

Regexクラスは、同じパターンを使う場合はインスタンスを使い回すのが基本です。

RegexOptions.Compiledを付けてコンパイル済みの正規表現を作成し、静的フィールドやシングルトンとして保持すると良いでしょう。

using System.Text.RegularExpressions;
public static class RegexCache
{
    public static readonly Regex NumericHyphenRegex = new Regex(
        @"^[0-9-]+$",
        RegexOptions.Compiled | RegexOptions.CultureInvariant
    );
}

このようにしておけば、毎回new Regex()を呼ぶ必要がなくなり、マッチング処理が高速化されます。

プーリングの活用

.NETの正規表現エンジン自体も内部でパターンのキャッシュを持っていますが、キャッシュサイズには制限があります。

大量の異なるパターンを頻繁に生成するとキャッシュミスが増え、パフォーマンス低下の原因になります。

そのため、可能な限りパターンを固定し、使い回すことが推奨されます。

動的にパターンを生成する場合は、生成済みのRegexインスタンスをプールして再利用する仕組みを自作することも検討してください。

非同期処理との相性

C#の非同期プログラミングasync/awaitはI/O待ちや長時間処理の効率化に有効ですが、正規表現のマッチング自体はCPUバウンドな処理であり、非同期メソッドとしては提供されていません。

CPUバウンド処理としての扱い

正規表現のマッチングはCPUリソースを使うため、非同期で直接実行してもスレッドの解放にはつながりません。

大量の文字列を検証する場合は、以下のような方法で非同期処理と組み合わせることが多いです。

  • Task.Runで別スレッドに処理をオフロード

CPU負荷の高い正規表現処理をTask.Runで別スレッドに移し、UIスレッドやメインスレッドのブロックを防ぐ。

  • バッチ処理で分割し、逐次的に非同期実行

大量のデータを小分割して非同期に処理し、応答性を保ちます。

using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
class Program
{
    private static readonly Regex NumericHyphenRegex = new Regex(@"^[0-9-]+$", RegexOptions.Compiled);
    static async Task<bool> ValidateAsync(string input)
    {
        return await Task.Run(() => NumericHyphenRegex.IsMatch(input));
    }
    static async Task Main()
    {
        string input = "123-456-7890";
        bool isValid = await ValidateAsync(input);
        Console.WriteLine($"入力 '{input}' の検証結果: {isValid}");
    }
}

この例では、Task.Runで正規表現のマッチングを別スレッドで実行し、非同期的に結果を取得しています。

UIアプリケーションなどでUIスレッドの応答性を保つのに有効です。

正規表現のパフォーマンス最適化は、キャッシュやプーリングで無駄なインスタンス生成を減らし、非同期処理ではCPUバウンド処理として適切にオフロードすることがポイントです。

これらを意識して設計すると、大量データや高頻度の検証でも快適に動作させられます。

デバッグ支援ツール

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

C#での開発においては、Visual Studioの内蔵ツールやコマンドラインユーティリティを活用することで、正規表現のデバッグや検証を効率的に行えます。

ここでは、Visual StudioのRegex EvaluatorとCLIユーティリティの活用方法を紹介します。

Visual StudioのRegex Evaluator

Visual Studioには、正規表現のテストやデバッグを支援する「Regex Evaluator」機能が組み込まれています。

これを使うと、正規表現パターンの動作をリアルタイムで確認でき、マッチする部分やグループの内容を視覚的に把握できます。

利用方法

  1. Visual Studioの「検索」機能を開く

メニューの「編集」→「検索と置換」→「ファイル内の検索」などを選択します。

  1. 正規表現モードを有効にする

検索ボックスの右側にある「正規表現を使用」アイコン(.*)をクリックして有効にします。

  1. パターンとテスト文字列を入力

検索ボックスに正規表現パターンを入力し、検索対象のテキストにテストしたい文字列を用意します。

  1. マッチ結果を確認

マッチした部分がハイライトされ、グループの内容も確認できます。

また、Visual Studioの「正規表現ツール」拡張機能をインストールすると、より高度な正規表現の作成・テストが可能です。

これには、パターンの構造を視覚化したり、リアルタイムでマッチ結果を表示したりする機能が含まれています。

メリット

  • IDE内で手軽に正規表現の動作確認ができる
  • マッチした部分やキャプチャグループを視覚的に把握できる
  • コード編集と並行して検証できるため効率的

CLIユーティリティの活用

Visual Studio以外にも、コマンドラインで正規表現をテストできるツールが多数存在します。

これらはスクリプトやCI/CDパイプラインでの自動検証にも利用可能です。

代表的なCLIツール

  • grep / egrep(Linux/macOS)

シンプルな正規表現の検索に使えます。

WindowsのWSL環境でも利用可能です。

  • rg (ripgrep)

高速で多機能な検索ツール。

正規表現のテストに便利です。

  • dotnet regex(.NET 7以降)

.NET SDKに含まれるdotnet regexコマンドは、C#の正規表現エンジンを使ってパターンのテストができます。

Windows、macOS、Linuxで動作します。

dotnet regexの使い方例

dotnet regex "^[0-9-]+$" --input "123-456-7890"

このコマンドは、入力文字列が指定した正規表現にマッチするかどうかを判定し、結果を表示します。

複数のテストケースをファイルにまとめて実行したり、詳細なマッチ情報を取得したりすることも可能です。

メリット

  • 環境を問わず利用できる
  • スクリプトやCI/CDに組み込みやすい
  • .NETの正規表現エンジンを使うため、実際の動作に近い検証ができる

これらのデバッグ支援ツールを活用することで、正規表現の動作確認や問題の特定がスムーズになります。

Visual Studioの内蔵機能は開発中の手軽な検証に、CLIユーティリティは自動化や環境を問わない検証に適しています。

用途に応じて使い分けてください。

まとめ

この記事では、C#で数字とハイフンのみを安全に判定する正規表現の基礎から応用までを解説しました。

基本パターンの作成ポイントや実装例、実用シナリオに加え、フロントエンド連携やユニットテスト、メンテナンス性向上の工夫も紹介しています。

さらに、セキュリティ対策やパフォーマンス最適化、デバッグ支援ツールの活用法も理解でき、実務での正規表現活用に役立つ内容となっています。

関連記事

Back to top button
目次へ