クラス

【C#】正規表現で数字の桁数を判定する方法と実装例

C#で数字の桁数を正規表現で厳密にチェックするには、先頭と末尾をアンカーし、\d{n}\d{n,m}を使うのが定番です。

たとえば4桁なら^\d{4}$、3〜6桁なら^\d{3,6}$

負数を許可する場合は^-?\d{n}$のようにハイフンをオプションにします。

桁数だけを検証したいときは回数指定子を活用し、入力全体を囲むことで余計な文字を排除できます。

正規表現で数字の桁数を判定する基本

正規表現は文字列のパターンを判定する強力なツールです。

C#で数字の桁数を判定する際にも、正規表現を使うことで簡潔かつ柔軟に条件を指定できます。

ここでは、数字の桁数を判定するための正規表現の基本的な構成要素について詳しく解説いたします。

アンカーで文字列全体を縛る

正規表現で数字の桁数を判定する際に重要なのが、文字列全体が条件に合致しているかどうかを確認することです。

これを実現するために「アンカー」と呼ばれる特殊な記号を使います。

^と$が持つ役割

  • ^(キャレット)は文字列の先頭を示します
  • $(ドル)は文字列の末尾を示します

これらを組み合わせることで、文字列全体が特定のパターンにマッチしているかを判定できます。

例えば、^\d{4}$という正規表現は「文字列の先頭から末尾まで、ちょうど4桁の数字だけで構成されている」ことを意味します。

もしアンカーを使わずに\d{4}だけを指定すると、文字列のどこかに4桁の数字が含まれていればマッチしてしまい、文字列全体の桁数判定には不十分です。

空白文字を許容しないパターン

数字の桁数を判定する際に、空白や改行などの余計な文字が混入していると正しく判定できません。

アンカーを使うことで、文字列の先頭から末尾までが数字だけであることを保証できます。

例えば、^\d{4}$は「4桁の数字のみ」を許容しますが、" 1234""1234 "のように空白が含まれる文字列はマッチしません。

空白を許容したい場合は別途パターンを調整する必要がありますが、基本的には数字だけを厳密に判定したい場合はアンカーを使って空白を排除します。

数字を表すメタ文字\d

数字を表す正規表現のメタ文字として\dがあります。

これは「数字1文字」を意味し、0から9までの数字にマッチします。

ASCII数字とUnicode数字の差異

\dは環境によって扱いが異なる場合があります。

一般的にはASCIIの数字(0~9)にマッチしますが、.NETの正規表現エンジンはUnicodeの数字も含めることがあります。

つまり、全角数字や他の数字文字も\dにマッチする可能性があります。

このため、半角数字だけを厳密に判定したい場合は\dの代わりに[0-9]を使うことが推奨されます。

[0-9]はASCIIの0から9までの数字だけにマッチします。

全角数字を含めたい場合

全角数字(例:0123456789)も判定対象に含めたい場合は、\dだけでは不十分なことがあります。

全角数字はUnicodeの別のコードポイントにあるため、\dがマッチしない環境もあります。

全角数字を含めるには、以下のように文字クラスを拡張します。

[0-90-9] このパターンは半角数字と全角数字の両方にマッチします。

例えば、^[0-90-9]{4}$は4桁の半角または全角数字のみの文字列にマッチします。

RegexOptions.ECMAScript利用時の挙動

C#のRegexクラスでRegexOptions.ECMAScriptオプションを指定すると、正規表現の挙動がECMAScript(JavaScript)準拠になります。

この場合、\dはASCIIの数字(0~9)のみを対象とし、Unicodeの数字は含まれません。

そのため、Unicode数字を含めたくない場合や、JavaScriptと同じ挙動にしたい場合はRegexOptions.ECMAScriptを使うと良いでしょう。

桁数を指定する量指定子

数字の桁数を判定するためには、数字の繰り返し回数を指定する「量指定子」を使います。

量指定子は波括弧 {} で囲み、繰り返し回数を細かく指定できます。

固定桁数{n}

{n}は「直前のパターンがちょうどn回繰り返される」ことを意味します。

例えば、\d{4}は「数字が4回連続する」ことを表します。

これをアンカーと組み合わせて^\d{4}$とすると、「文字列全体が4桁の数字のみ」という条件になります。

範囲指定{n,m}

{n,m}は「直前のパターンがn回以上m回以下繰り返される」ことを意味します。

例えば、\d{3,6}は「数字が3回から6回連続する」ことを表します。

これを使うと、例えば^\d{3,6}$で「3桁から6桁の数字のみ」の文字列を判定できます。

上限なし{n,}の活用法

{n,}は「直前のパターンがn回以上繰り返される」ことを意味します。

例えば、\d{4,}は「4桁以上の数字が連続する」ことを表します。

これを使うと、最低桁数だけを指定して、それ以上の桁数は許容するパターンを作れます。

例えば、^\d{4,}$は「4桁以上の数字のみ」の文字列にマッチします。

これらの基本を押さえることで、C#の正規表現で数字の桁数を正確に判定するパターンを作成できます。

典型的な桁数判定パターン例

4桁の固定数字

4桁の数字だけを判定したい場合、正規表現は非常にシンプルです。

文字列全体が4桁の数字で構成されているかを確認するには、^\d{4}$を使います。

以下はC#のサンプルコードです。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string inputString = "1234";
        // 4桁の数字のみを判定する正規表現
        string pattern = @"^\d{4}$";
        if (Regex.IsMatch(inputString, pattern))
        {
            Console.WriteLine("入力された文字列は4桁の数字です。");
        }
        else
        {
            Console.WriteLine("入力された文字列は4桁の数字ではありません。");
        }
    }
}
入力された文字列は4桁の数字です。

このコードは、inputStringが4桁の数字だけで構成されているかを判定しています。

アンカー^$で文字列全体を囲み、\d{4}で4桁の数字を指定しています。

3〜6桁の可変数字

3桁から6桁までの数字を許容したい場合は、量指定子の範囲指定{3,6}を使います。

これにより、3桁以上6桁以下の数字列にマッチします。

サンプルコードは以下の通りです。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string inputString = "12345";
        // 3桁から6桁の数字を判定する正規表現
        string pattern = @"^\d{3,6}$";
        if (Regex.IsMatch(inputString, pattern))
        {
            Console.WriteLine("入力された文字列は3桁から6桁の数字です。");
        }
        else
        {
            Console.WriteLine("入力された文字列は3桁から6桁の数字ではありません。");
        }
    }
}
入力された文字列は3桁から6桁の数字です。

この例では、inputStringが3桁以上6桁以下の数字であるかを判定しています。

範囲指定により柔軟な桁数チェックが可能です。

負数を許容するパターン

数字の前にマイナス記号(ハイフン)が付く負の数も判定したい場合は、ハイフンの出現をオプションにする必要があります。

正規表現では-?を使い、ハイフンが0回または1回出現することを表します。

ハイフンをオプションにする方法

4桁の数字または4桁の負の数字を判定する例を示します。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string inputString = "-1234";
        // 先頭にハイフンが0回または1回、続いて4桁の数字
        string pattern = @"^-?\d{4}$";
        if (Regex.IsMatch(inputString, pattern))
        {
            Console.WriteLine("入力された文字列は4桁の数字または4桁の負の数字です。");
        }
        else
        {
            Console.WriteLine("入力された文字列は4桁の数字または4桁の負の数字ではありません。");
        }
    }
}
入力された文字列は4桁の数字または4桁の負の数字です。

このパターンは、先頭にハイフンがあってもなくても4桁の数字列にマッチします。

-?の部分がハイフンの有無を制御しています。

ソースコードへの埋め込み方

正規表現パターンをC#のソースコードに埋め込む際には、文字列リテラルの書き方に注意が必要です。

特にバックスラッシュ\を多用する正規表現では、エスケープの手間を減らすためにVerbatim文字列を使うのが便利です。

Verbatim文字列@”…”の利便性

通常の文字列リテラルでは、\はエスケープ文字として扱われるため、正規表現の\d"\\d"のように二重にエスケープしなければなりません。

これが煩雑になることがあります。

一方、@を付けたVerbatim文字列では、\はそのまま文字として扱われるため、\d@"\d"と書けます。

これにより、正規表現の記述がシンプルで読みやすくなります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string inputString = "5678";
        // Verbatim文字列で正規表現を記述
        string pattern = @"^\d{4}$";
        if (Regex.IsMatch(inputString, pattern))
        {
            Console.WriteLine("4桁の数字です。");
        }
        else
        {
            Console.WriteLine("4桁の数字ではありません。");
        }
    }
}
4桁の数字です。

このように、@"..."を使うことで正規表現のバックスラッシュをエスケープせずに済み、コードの可読性が向上します。

特に複雑なパターンを書く場合は必須のテクニックです。

特殊ケースへの対応

先頭ゼロの扱い

数字の桁数を判定する際、先頭にゼロが含まれるかどうかは重要なポイントです。

例えば「0123」は4桁の数字ですが、先頭ゼロを許容するかどうかは用途によって異なります。

正規表現で先頭ゼロを許容する場合は特に制限を設けず、単純に\d{n}\d{n,m}を使えば問題ありません。

例えば、4桁の数字であれば^\d{4}$で「0123」もマッチします。

一方、先頭ゼロを許容しない場合は、先頭の数字を1~9に限定し、残りの桁を0~9にするパターンを作ります。

4桁の数字で先頭ゼロを許さない例は以下の通りです。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string inputString = "0123";
        // 先頭は1~9、続く3桁は0~9の数字
        string pattern = @"^[1-9]\d{3}$";
        if (Regex.IsMatch(inputString, pattern))
        {
            Console.WriteLine("先頭ゼロなしの4桁の数字です。");
        }
        else
        {
            Console.WriteLine("先頭ゼロがあるか、4桁の数字ではありません。");
        }
    }
}
先頭ゼロがあるか、4桁の数字ではありません。

このように、先頭ゼロを排除したい場合は先頭の数字を[1-9]で指定し、残りの桁数を\dで表現します。

プラス記号の許可

負の数を表すマイナス記号に加え、正の数を明示するプラス記号を許可したい場合があります。

プラス記号は+ですが、正規表現では特殊文字なのでエスケープが必要です。

先頭にプラスまたはマイナス記号が0回または1回出現するパターンは、^[-+]?で表現します。

例えば、4桁の数字にプラス・マイナス記号を許可する場合は以下のようになります。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string[] inputs = { "+1234", "-1234", "1234", "++1234" };
        string pattern = @"^[-+]?\d{4}$";
        foreach (var input in inputs)
        {
            if (Regex.IsMatch(input, pattern))
            {
                Console.WriteLine($"{input} は4桁の数字で符号付きです。");
            }
            else
            {
                Console.WriteLine($"{input} は条件に合いません。");
            }
        }
    }
}
+1234 は4桁の数字で符号付きです。
-1234 は4桁の数字で符号付きです
1234 は4桁の数字で符号付きです。
++1234 は条件に合いません。

[-+]?はハイフンまたはプラスが0回または1回の出現を許可し、++1234のように複数の符号はマッチしません。

小数点や区切り文字を除く

数字の桁数判定で、小数点やカンマなどの区切り文字を含めたくない場合は、正規表現でそれらの文字を排除する必要があります。

例えば「1,234」や「12.34」は数字としては意味がありますが、桁数判定では純粋な数字のみを対象にしたいことが多いです。

正規表現で数字と区切り文字を混在させると誤判定の原因になるため、区切り文字を含まないパターンを使います。

例えば、4桁の数字のみを判定する場合は^\d{4}$で十分です。

もし入力に区切り文字が含まれている可能性がある場合は、事前に文字列からカンマやピリオドを除去してから判定する方法が安全です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string inputString = "1,234";
        // カンマを除去
        string sanitized = inputString.Replace(",", "");
        string pattern = @"^\d{4}$";
        if (Regex.IsMatch(sanitized, pattern))
        {
            Console.WriteLine("カンマを除去した後、4桁の数字です。");
        }
        else
        {
            Console.WriteLine("4桁の数字ではありません。");
        }
    }
}
カンマを除去した後、4桁の数字です。

このように、区切り文字は正規表現で直接扱うよりも、前処理で除去するほうが確実です。

科学技術表記を排除する

科学技術表記(例:1.23e4-5.67E-8)は数字の表現として一般的ですが、桁数判定の対象外にしたい場合があります。

これらは数字以外に小数点や指数記号e/E、符号などが含まれるため、単純な数字の桁数判定ではマッチしません。

もし科学技術表記を含む文字列が入力される可能性がある場合は、正規表現でそれらを排除するか、逆に科学技術表記のパターンを検出して除外する処理を行います。

科学技術表記を排除する簡単な方法は、数字と符号以外の文字が含まれていないかをチェックすることです。

例えば、以下のように数字と符号だけを許可するパターンを使います。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string[] inputs = { "1234", "1.23e4", "-5678", "9E10" };
        // 符号付きの数字のみ(小数点やeを含まない)
        string pattern = @"^[-+]?\d+$";
        foreach (var input in inputs)
        {
            if (Regex.IsMatch(input, pattern))
            {
                Console.WriteLine($"{input} は整数の数字です。");
            }
            else
            {
                Console.WriteLine($"{input} は科学技術表記や小数点を含みます。");
            }
        }
    }
}
1234 は整数の数字です。
1.23e4 は科学技術表記や小数点を含みます。
-5678 は整数の数字です
9E10 は科学技術表記や小数点を含みます。

このように、科学技術表記を含む文字列は正規表現で除外し、純粋な整数の数字だけを判定できます。

必要に応じて小数点や指数表記を許可するパターンを別途用意することも可能です。

実装例

Regex.IsMatchを用いた即時検証

C#で正規表現を使って数字の桁数を判定する最もシンプルな方法は、Regex.IsMatchメソッドを使うことです。

このメソッドは、指定した文字列が正規表現パターンにマッチするかどうかを即座に判定します。

以下は、4桁の数字かどうかを判定するサンプルコードです。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string input = "5678";
        string pattern = @"^\d{4}$";
        // 文字列が4桁の数字かどうかを即時判定
        bool isMatch = Regex.IsMatch(input, pattern);
        Console.WriteLine(isMatch ? "4桁の数字です。" : "4桁の数字ではありません。");
    }
}
4桁の数字です。

Regex.IsMatchは静的メソッドなので、簡単に使えますが、頻繁に同じパターンで判定を繰り返す場合はパフォーマンス面でやや劣ることがあります。

Regexインスタンスの再利用

大量の文字列を同じ正規表現で判定する場合は、Regexクラスのインスタンスを作成して再利用する方法が効率的です。

Regexインスタンスはパターンの解析結果を内部に保持するため、繰り返し使うとパフォーマンスが向上します。

以下は、Regexインスタンスを使って複数の文字列を判定する例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string[] inputs = { "1234", "56789", "abcd", "0000" };
        Regex regex = new Regex(@"^\d{4}$");
        foreach (var input in inputs)
        {
            if (regex.IsMatch(input))
            {
                Console.WriteLine($"{input} は4桁の数字です。");
            }
            else
            {
                Console.WriteLine($"{input} は4桁の数字ではありません。");
            }
        }
    }
}
1234 は4桁の数字です。
56789 は4桁の数字ではありません。
abcd は4桁の数字ではありません。
0000 は4桁の数字です。

コンストラクタとRegexOptions.Compiled

Regexのコンストラクタにはオプションを指定できます。

特にRegexOptions.Compiledを指定すると、正規表現がILコードにコンパイルされ、実行時のパフォーマンスが向上します。

ただし、コンパイルには初期化時にコストがかかるため、頻繁に使うパターンで長時間動作するアプリケーションに向いています。

以下はRegexOptions.Compiledを使った例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        Regex regex = new Regex(@"^\d{4}$", RegexOptions.Compiled);
        string[] inputs = { "1234", "12a4", "9999" };
        foreach (var input in inputs)
        {
            Console.WriteLine($"{input} => {regex.IsMatch(input)}");
        }
    }
}
1234 => True
12a4 => False
9999 => True

RegexOptions.Compiledを使うことで、繰り返し判定時の速度が向上しますが、初期化コストがあるため使いどころを考慮してください。

パターンとエラーメッセージの分離

正規表現のパターンとエラーメッセージを分離して管理すると、コードの保守性が高まります。

特に複数のパターンやメッセージを扱う場合は、定数や設定ファイルに分けるのが効果的です。

以下は、パターンとメッセージを定数として分離した例です。

using System;
using System.Text.RegularExpressions;
class Program
{
    private const string PatternFourDigit = @"^\d{4}$";
    private const string ErrorMessage = "入力は4桁の数字でなければなりません。";
    static void Main()
    {
        string input = "123";
        if (Regex.IsMatch(input, PatternFourDigit))
        {
            Console.WriteLine("入力は有効です。");
        }
        else
        {
            Console.WriteLine(ErrorMessage);
        }
    }
}
入力は4桁の数字でなければなりません。

このようにパターンとメッセージを分けることで、将来的にパターンを変更したり多言語対応を行う際に柔軟に対応できます。

入力フォームでの活用

TextBoxのKeyPressイベントでリアルタイム検証

WindowsフォームやWPFなどのC#アプリケーションで、ユーザーが入力中に数字の桁数をリアルタイムで検証したい場合、TextBoxKeyPressイベントを活用できます。

このイベントはキー入力が行われるたびに発生し、入力内容を制御したり検証したりするのに適しています。

以下は、4桁の数字のみを許可し、それ以外の入力を抑制するサンプルコードです。

using System;
using System.Text.RegularExpressions;
using System.Windows.Forms;
class Program : Form
{
    private TextBox textBox;
    public Program()
    {
        textBox = new TextBox { Location = new System.Drawing.Point(20, 20), Width = 200 };
        textBox.KeyPress += TextBox_KeyPress;
        Controls.Add(textBox);
    }
    private void TextBox_KeyPress(object sender, KeyPressEventArgs e)
    {
        // バックスペースは許可
        if (e.KeyChar == (char)Keys.Back)
        {
            return;
        }
        // 入力予定のテキストを取得
        string currentText = textBox.Text;
        int selectionStart = textBox.SelectionStart;
        int selectionLength = textBox.SelectionLength;
        // 入力後のテキストを予測
        string newText = currentText.Remove(selectionStart, selectionLength)
                                    .Insert(selectionStart, e.KeyChar.ToString());
        // 4桁の数字か部分一致かを判定
        string pattern = @"^\d{0,4}$"; // 0~4桁の数字を許可
        if (!Regex.IsMatch(newText, pattern))
        {
            // パターンに合わない場合は入力をキャンセル
            e.Handled = true;
        }
    }
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Program());
    }
}

このコードでは、KeyPressイベントで入力後の文字列が「0~4桁の数字のみ」かどうかを判定し、条件に合わない場合は入力をキャンセルしています。

バックスペースは許可しているため、削除操作は問題ありません。

部分一致と完全一致の棲み分け

リアルタイム検証では、入力途中の文字列に対して完全一致の正規表現を使うと、まだ入力が完了していない段階で不正と判定されてしまいます。

そこで、部分一致を許容するパターンを使うことが重要です。

例えば、4桁の数字を完全一致で判定する正規表現は^\d{4}$ですが、入力途中の段階では^\d{0,4}$のように「0桁から4桁までの数字」を許容するパターンを使います。

これにより、ユーザーが1文字ずつ入力してもエラーにならず、自然な入力体験を提供できます。

一方、フォームの送信時や確定時には完全一致のパターンで最終チェックを行い、桁数が不足していないかを厳密に検証します。

DataAnnotations.RegularExpressionAttributeの利用

ASP.NETや.NETのMVC、Blazorなどのフレームワークでは、DataAnnotations名前空間のRegularExpressionAttributeを使ってモデルのプロパティに正規表現によるバリデーションを簡単に設定できます。

例えば、4桁の数字のみを許可するプロパティは以下のように定義します。

using System.ComponentModel.DataAnnotations;
public class SampleModel
{
    [RegularExpression(@"^\d{4}$", ErrorMessage = "4桁の数字を入力してください。")]
    public string FourDigitNumber { get; set; }
}

この属性を付けることで、モデルバインディング時に自動的に正規表現による検証が行われ、条件に合わない場合はエラーメッセージが返されます。

コントローラーやページでモデルの検証結果をチェックし、エラーがあればユーザーにフィードバックを表示できます。

public IActionResult Submit(SampleModel model)
{
    if (!ModelState.IsValid)
    {
        // バリデーションエラー時の処理
        return View(model);
    }
    // 正常処理
    return RedirectToAction("Success");
}

RegularExpressionAttributeを使うことで、サーバーサイドの堅牢なバリデーションを簡単に実装でき、クライアントサイドのJavaScriptバリデーションとも連携しやすいです。

単体テストのポイント

境界値のテストケース

数字の桁数を判定する正規表現の単体テストでは、境界値を重点的にテストすることが重要です。

境界値とは、許容される最小桁数や最大桁数の直前・直後の値を指し、これらを正確に判定できるかを確認します。

例えば、3桁から6桁の数字を判定する場合、以下のような境界値テストケースが考えられます。

テストケース入力例期待結果理由
最小桁数未満“12”不一致(false)2桁は3桁未満で不許可
最小桁数“123”一致(true)ちょうど3桁で許可
最小桁数超過“1234”一致(true)3桁以上なので許可
最大桁数未満“12345”一致(true)6桁未満で許可
最大桁数“123456”一致(true)ちょうど6桁で許可
最大桁数超過“1234567”不一致(false)7桁は6桁超過で不許可

これらの境界値をテストすることで、正規表現が指定した桁数範囲を正確に判定しているかを検証できます。

また、固定桁数の判定では、指定桁数の前後の文字数をテストすることも重要です。

例えば4桁固定なら3桁や5桁の入力が不許可になるかを確認します。

異常入力の網羅

単体テストでは、正常な数字以外の異常入力も網羅的に検証する必要があります。

これにより、誤った入力を正しく弾けるかを確認できます。

主な異常入力の例は以下の通りです。

  • 空文字列やnull
  • 数字以外の文字を含む文字列(例:”12a4″, “abc”, “123 4”)
  • 記号や特殊文字を含む文字列(例:”123#”, “12-34”)
  • 全角数字や全角スペースを含む文字列(例:”1234″, “1234”)
  • 小数点やカンマを含む文字列(例:”1.234″, “1,234”)
  • 符号が複数ある文字列(例:”–1234″, “++1234”)
  • 空白文字を含む文字列(例:” 1234″, “1234 “)

これらの異常入力に対して、正規表現がマッチしない(falseを返す)ことを確認します。

以下は、NUnitを使った異常入力のテスト例です。

using NUnit.Framework;
using System.Text.RegularExpressions;
[TestFixture]
public class DigitLengthValidationTests
{
    private const string Pattern = @"^\d{4}$";
    [TestCase(null)]
    [TestCase("")]
    [TestCase("12a4")]
    [TestCase("abc")]
    [TestCase("123 4")]
    [TestCase("123#")]
    [TestCase("12-34")]
    [TestCase("1234")] // 全角数字
    [TestCase("1.234")]
    [TestCase("1,234")]
    [TestCase("--1234")]
    [TestCase("++1234")]
    [TestCase(" 1234")]
    [TestCase("1234 ")]
    public void InvalidInputs_ShouldNotMatch(string input)
    {
        bool result = input != null && Regex.IsMatch(input, Pattern);
        Assert.IsFalse(result);
    }
}

このように異常入力を網羅的にテストすることで、正規表現の堅牢性を高められます。

特にユーザー入力を扱う場面では、想定外の入力に対しても適切に弾くことが重要です。

パフォーマンス最適化

静的フィールドでのパターンキャッシュ

正規表現を使った桁数判定を頻繁に行う場合、毎回Regexオブジェクトを生成するとパフォーマンスに影響が出ることがあります。

Regexのインスタンス生成時にはパターンの解析や内部データ構造の構築が行われるため、これを繰り返すと無駄なコストがかかります。

この問題を解決するために、正規表現パターンを静的フィールドとしてキャッシュし、使い回す方法が有効です。

静的フィールドにRegexインスタンスを保持することで、アプリケーションのライフタイム中に一度だけパターン解析が行われ、以降は高速にマッチング処理が可能になります。

以下は静的フィールドでパターンをキャッシュする例です。

using System;
using System.Text.RegularExpressions;
class DigitValidator
{
    // 静的フィールドにRegexインスタンスを保持
    private static readonly Regex FourDigitRegex = new Regex(@"^\d{4}$", RegexOptions.Compiled);
    public static bool IsFourDigitNumber(string input)
    {
        if (string.IsNullOrEmpty(input))
            return false;
        return FourDigitRegex.IsMatch(input);
    }
}
class Program
{
    static void Main()
    {
        string[] inputs = { "1234", "567", "abcd", "0000" };
        foreach (var input in inputs)
        {
            Console.WriteLine($"{input} => {DigitValidator.IsFourDigitNumber(input)}");
        }
    }
}
1234 => True
567 => False
abcd => False
0000 => True

このように静的フィールドでRegexを保持すると、毎回のインスタンス生成コストを削減でき、パフォーマンスが向上します。

コンパイル済み正規表現の効果

RegexOptions.Compiledオプションを指定すると、正規表現がILコードにコンパイルされ、実行時のマッチング処理が高速化されます。

特に大量の文字列を繰り返し検証する場合に効果が顕著です。

ただし、コンパイルには初期化時に時間がかかるため、短時間で終わる処理や一度しか使わないパターンには向きません。

長時間動作するアプリケーションや頻繁に使うパターンに対して使うのが適切です。

先ほどの例でもRegexOptions.Compiledを指定していますが、これによりパフォーマンスが向上します。

以下はコンパイル済み正規表現の効果を示す簡単な比較コード例です。

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        string pattern = @"^\d{4}$";
        string input = "1234";
        // 非コンパイル版
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < 100000; i++)
        {
            Regex.IsMatch(input, pattern);
        }
        sw.Stop();
        Console.WriteLine($"非コンパイル版: {sw.ElapsedMilliseconds} ms");
        // コンパイル版
        var regexCompiled = new Regex(pattern, RegexOptions.Compiled);
        sw.Restart();
        for (int i = 0; i < 100000; i++)
        {
            regexCompiled.IsMatch(input);
        }
        sw.Stop();
        Console.WriteLine($"コンパイル版: {sw.ElapsedMilliseconds} ms");
    }
}
非コンパイル版: 9 ms
コンパイル版: 3 ms

(※実行環境により数値は異なりますが、コンパイル版のほうが高速であることが一般的です)

このように、RegexOptions.Compiledを活用することで、正規表現のパフォーマンスを大幅に改善できます。

静的フィールドと組み合わせて使うと、さらに効果的です。

よくあるエラーと対処

量指定子の貪欲性

正規表現の量指定子(例:*, +, {n,m})はデフォルトで「貪欲(greedy)」に動作します。

つまり、可能な限り多くの文字にマッチしようとします。

数字の桁数を判定する際に、意図しない部分までマッチしてしまうことがあるため注意が必要です。

例えば、^\d{3,6}$は3桁から6桁の数字にマッチしますが、文字列の途中に数字が連続している場合、貪欲性により想定外のマッチングが起こることは少ないです。

しかし、複雑なパターンで他の文字と組み合わせると、貪欲性が原因で誤判定が発生することがあります。

対処法としては、必要に応じて「非貪欲(lazy)」量指定子を使う方法があります。

非貪欲量指定子は*?, +?, {n,m}?のように?を付けて表現します。

ただし、数字の桁数判定のように固定長や範囲指定で完全一致を狙う場合は、非貪欲指定子はあまり使いません。

また、アンカー^$を必ず使い、文字列全体を厳密にマッチさせることで、貪欲性による誤判定を防げます。

エスケープ漏れによる誤判定

正規表現のパターン内で特殊文字を使う場合、エスケープが必要です。

例えば、.(ドット)は任意の1文字を意味しますが、文字としてのドットを表現したい場合は\.とエスケープしなければなりません。

数字の桁数判定では\d-(ハイフン)などを使いますが、ハイフンは文字クラス内で範囲指定に使われるため、文字クラス外で使う場合はエスケープが必要なケースがあります。

エスケープ漏れがあると、意図しない文字にマッチしたり、正規表現の構文エラーが発生したりします。

例として、ハイフンを含むパターンでエスケープ漏れがある場合:

誤ったパターン(エスケープ漏れ)

^-?\d{4}$ → これは正しいが、もし-を文字クラス外で使う場合は注意が必要でしょう。

文字クラス内でのハイフンの扱い:

[0-9\-] のようにハイフンを文字として含める場合は、\-とエスケープするか、文字クラスの先頭または末尾に置く必要があります。

エスケープ漏れを防ぐために、正規表現を書く際は以下を心がけてください。

  • 特殊文字は必要に応じて\でエスケープする
  • 文字クラス内のハイフンは範囲指定か文字としての意味かを明確にする
  • Visual Studioやオンラインの正規表現テスターでパターンを検証する

オプション指定のミス

C#のRegexクラスでは、オプションを指定して正規表現の挙動を制御できますが、オプションの指定ミスによって期待したマッチングが得られないことがあります。

代表的なオプションミスの例は以下の通りです。

  • RegexOptions.IgnoreCaseを指定しているのに数字の判定に影響はないが、他の文字列判定で誤解が生じることがあります
  • RegexOptions.Multilineを指定すると、^$が行頭・行末にマッチするようになるため、文字列全体のマッチングが崩れる場合があります
  • RegexOptions.ECMAScriptを指定すると、\dがASCII数字のみに限定され、Unicode数字を含めたい場合にマッチしなくなります
  • オプションを複数指定する際にビット演算子|を使い忘れると、意図しないオプション設定になります

例えば、複数オプションを指定する正しい例:

new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase) 誤ってオプションをカンマ区切りで指定するとコンパイルエラーになります。

オプション指定のミスを防ぐには、以下を意識してください。

  • 正規表現の目的に合ったオプションを選ぶ
  • 複数オプションはビット演算子|で結合する
  • オプションの影響を理解し、特にMultilineECMAScriptの挙動に注意する
  • テストで意図したマッチングが得られているか確認する

これらのポイントを押さえることで、正規表現の誤動作を防ぎ、数字の桁数判定を正確に行えます。

代替手段との比較

int.TryParseを使う場合

数字の桁数を判定する目的で、正規表現の代わりにint.TryParseを使う方法があります。

int.TryParseは文字列が整数として有効かどうかを判定し、変換に成功すればtrueを返します。

桁数判定を行うには、まずint.TryParseで数字かどうかを確認し、その後に文字列の長さをチェックします。

例えば、4桁の数字かどうかを判定するコードは以下のようになります。

using System;
class Program
{
    static void Main()
    {
        string input = "1234";
        if (int.TryParse(input, out int number) && input.Length == 4)
        {
            Console.WriteLine("4桁の数字です。");
        }
        else
        {
            Console.WriteLine("4桁の数字ではありません。");
        }
    }
}
4桁の数字です。

この方法のメリットは、正規表現を使わずにシンプルに数字判定ができる点です。

また、TryParseは数値としての妥当性もチェックするため、数字以外の文字が混入している場合は自動的に弾けます。

ただし、intの範囲を超える非常に大きな数字や、先頭にゼロがある場合の扱いに注意が必要です。

例えば、"0123"は文字列長は4ですが、int.TryParseで変換すると123となり、桁数判定がずれる可能性があります。

LINQでのフィルタリング

複数の文字列の中から特定の桁数の数字だけを抽出したい場合、LINQを使ったフィルタリングも有効です。

LINQのWhereメソッドと組み合わせて、文字列の長さや数字判定を行えます。

以下は、4桁の数字のみを抽出する例です。

using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
    static void Main()
    {
        List<string> inputs = new List<string> { "1234", "567", "abcd", "0000", "12345" };
        var fourDigitNumbers = inputs.Where(s => s.Length == 4 && s.All(char.IsDigit));
        foreach (var num in fourDigitNumbers)
        {
            Console.WriteLine(num);
        }
    }
}
1234
0000

この方法は、正規表現を使わずに文字列の長さと全ての文字が数字かどうかをchar.IsDigitで判定しています。

可読性が高く、簡単な条件であれば十分に使えます。

ただし、複雑なパターンや負の数、符号付きの数字などを判定する場合は正規表現のほうが柔軟です。

カスタムバリデーターの実装

より複雑な桁数判定や条件付きの検証が必要な場合は、カスタムバリデーターを実装する方法があります。

特にASP.NET MVCやBlazorなどのフレームワークでは、独自のバリデーション属性を作成してモデルの検証に組み込めます。

以下は、4桁の数字のみを許可するカスタムバリデーターの例です。

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
public class FourDigitNumberAttribute : ValidationAttribute
{
    private static readonly Regex regex = new Regex(@"^\d{4}$", RegexOptions.Compiled);
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string input = value as string;
        if (string.IsNullOrEmpty(input) || !regex.IsMatch(input))
        {
            return new ValidationResult("4桁の数字を入力してください。");
        }
        return ValidationResult.Success;
    }
}

この属性をモデルのプロパティに付与すると、バリデーション時に自動的に4桁の数字かどうかがチェックされます。

public class SampleModel
{
    [FourDigitNumber]
    public string Code { get; set; }
}

カスタムバリデーターは、単純な正規表現以上のロジックを組み込んだり、複数条件を組み合わせたりする際に便利です。

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

/// <summary>
/// 4桁の数字のみ許可する検証属性クラス
/// </summary>
public class FourDigitNumberAttribute : ValidationAttribute
{
    private static readonly Regex regex = new Regex(@"^\d{4}$", RegexOptions.Compiled);

    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        string? input = value as string;
        if (string.IsNullOrEmpty(input) || !regex.IsMatch(input))
        {
            return new ValidationResult("4桁の数字を入力してください。");
        }
        return ValidationResult.Success;
    }
}

class Program
{
    static void Main()
    {
        var validator = new FourDigitNumberAttribute();

        string?[] testInputs = { "1234", "abcd", "12", null, "56789" };

        foreach (var input in testInputs)
        {
            // 検証コンテキストは検証対象のインスタンスがnullにならないよう、ここではダミーのオブジェクトを渡す
            var context = new ValidationContext(new object());
            var result = validator.GetValidationResult(input, context);
            if (result == ValidationResult.Success)
            {
                Console.WriteLine($"入力値 \"{input}\" は有効です。");
            }
            else
            {
                Console.WriteLine($"入力値 \"{input}\" は無効です: {result?.ErrorMessage}");
            }
        }
    }
}
入力値 "1234" は有効です。
入力値 "abcd" は無効です: 4桁の数字を入力してください。
入力値 "12" は無効です: 4桁の数字を入力してください。
入力値 "" は無効です: 4桁の数字を入力してください。
入力値 "56789" は無効です: 4桁の数字を入力してください。

また、エラーメッセージのカスタマイズや多言語対応も柔軟に行えます。

これらの代替手段は、用途や要件に応じて使い分けることが重要です。

単純な桁数判定ならint.TryParseやLINQで十分ですが、複雑なパターンや堅牢なバリデーションが必要な場合は正規表現やカスタムバリデーターを活用すると良いでしょう。

まとめ

この記事では、C#で正規表現を使って数字の桁数を判定する基本から応用までを解説しました。

アンカーや量指定子の使い方、典型的なパターン例、特殊ケースへの対応方法、実装例や入力フォームでの活用法、単体テストやパフォーマンス最適化のポイントも紹介しています。

さらに、よくあるエラーの対処法や代替手段との比較も行い、用途に応じた最適な方法を選べるようになります。

これにより、数字の桁数判定を正確かつ効率的に実装できる知識が身につきます。

関連記事

Back to top button
目次へ