みなさんこんにちは!ヒロポンです!
C# 業務系の入力検証で、こんなことないですか??
- 新規登録フォームでメール検証の正規表現を書こうとして「正解の書き方が思い出せない」
- 電話番号のハイフン有無で正規表現が複雑になりすぎて挫折
- 全角混入チェックの Unicode 範囲指定が思い出せない
Regex.IsMatchで^$忘れて 部分一致で意図しないマッチ
俺も最初の業務系 WinForms 案件で新規登録フォーム作った時、メール検証で RFC5322 厳密版 をネットからコピペして、テストで「正常なメールが弾かれる」報告上がって30分溶かしました。.NET Framework 4.7.2 / VS2019 / C# 7.3 の構成で、結局簡易版に戻したっていうオチ。
ん?普通にメール検証くらい簡単じゃないの??って思いますよね。
業務系の正規表現は 「厳密版より現実版」 で書くのが鉄則。今回は業務系入力検証で頻出する C# 正規表現の5パターンを、コード付きで整理します。コード6本構成。
TL;DR
- 業務系の正規表現は 厳密版より現実版 で書く(メール検証 RFC5322 厳密版は使わない)
- 5パターン: メール / 電話番号 / 郵便番号 / 全角半角判定 / 数値・日付フォーマット
- 大量呼び出しは
RegexOptions.Compiledで高速化(10万件で 3秒 → 0.5秒の体感) IsMatchの^$忘れに注意(部分一致で意図しないマッチ)
パターン1: メールアドレス検証 — 簡易版が現実解
業務系のメール検証で RFC5322 厳密版を使うのは事故の元。正規表現が数百文字になって、テストで正常なメールが弾かれるクレームが上がります。
using System.Text.RegularExpressions;
public static class EmailValidator
{
// ✅ 業務系の現実版(簡易・ほぼ十分)
private static readonly Regex EmailRegex = new Regex(
@"^[^@\s]+@[^@\s]+\.[^@\s]+$",
RegexOptions.Compiled);
public static bool IsValid(string email)
{
if (string.IsNullOrWhiteSpace(email)) return false;
return EmailRegex.IsMatch(email);
}
}
// 使い方
class Program
{
static void Main()
{
Console.WriteLine(EmailValidator.IsValid("user@example.com")); // True
Console.WriteLine(EmailValidator.IsValid("user.name@sub.co.jp")); // True
Console.WriteLine(EmailValidator.IsValid("invalid@@example")); // False
Console.WriteLine(EmailValidator.IsValid("no-at-sign.com")); // False
}
}
正規表現の意味:
^$— 先頭末尾アンカー(完全一致)[^@\s]+— @ と空白以外の1文字以上@— @ 記号[^@\s]+— ドメイン部\.— ドット(エスケープ)[^@\s]+— TLD
この簡易版で 業務系の登録フォーム検証は十分。本格的な検証が必要なら、メール送信時の到達確認(ダブルオプトイン)に委ねるのが業務SE 鉄則。
俺も独立後の案件で、RFC5322 厳密版を入れたシステムを引き継いだ時、「user+tag@example.com が弾かれる」「user.name+filter@example.com が弾かれる」のクレーム対応に追われた経験があります。厳密版を入れると保守コストが跳ね上がる ので、業務系では簡易版一択です。
パターン2: 電話番号検証 — 国内 + 国際対応
電話番号の正規表現は、国内専用と国際対応で書き方が違います。
using System.Text.RegularExpressions;
public static class PhoneValidator
{
// ✅ 国内電話番号(先頭 0 + ハイフン任意)
// 例: 03-1234-5678 / 090-1234-5678 / 0312345678
private static readonly Regex DomesticPhoneRegex = new Regex(
@"^0\d{1,4}-?\d{1,4}-?\d{4}$",
RegexOptions.Compiled);
// ✅ 国際対応版(+ 記号 + 国番号 + 国内番号)
// 例: +81-3-1234-5678 / 03-1234-5678 / +1 555 123 4567
private static readonly Regex InternationalPhoneRegex = new Regex(
@"^(\+?\d{1,4}[-\s]?)?\d{2,5}[-\s]?\d{2,5}[-\s]?\d{2,5}$",
RegexOptions.Compiled);
public static bool IsValidDomestic(string phone)
{
if (string.IsNullOrWhiteSpace(phone)) return false;
return DomesticPhoneRegex.IsMatch(phone);
}
public static bool IsValidInternational(string phone)
{
if (string.IsNullOrWhiteSpace(phone)) return false;
return InternationalPhoneRegex.IsMatch(phone);
}
}
// 使い方
class Program
{
static void Main()
{
// 国内
Console.WriteLine(PhoneValidator.IsValidDomestic("03-1234-5678")); // True
Console.WriteLine(PhoneValidator.IsValidDomestic("0312345678")); // True
Console.WriteLine(PhoneValidator.IsValidDomestic("090-1234-5678")); // True
Console.WriteLine(PhoneValidator.IsValidDomestic("+81-3-1234-5678")); // False
// 国際
Console.WriteLine(PhoneValidator.IsValidInternational("+81-3-1234-5678")); // True
Console.WriteLine(PhoneValidator.IsValidInternational("+1 555 123 4567")); // True
}
}
ハマりポイント。ハイフンを -? で任意にする のがブレ吸収のポイント。ユーザーは 0312345678 / 03-1234-5678 / 03 1234 5678 を混在で入力するので、[-\s]? で両方を許容するのが現実解。
業務系で国際電話を受け付ける必要があるかは、システム要件次第。海外取引のある会社のシステムなら最初から国際対応、国内オンリーなら厳密にすればいい、という判断軸を持つといい感じです。
パターン3: 郵便番号検証 — 7桁ハイフン形式
国内向けの郵便番号は7桁ハイフン形式で固定。書き方はシンプルです。
using System.Text.RegularExpressions;
public static class PostalCodeValidator
{
// ✅ 郵便番号: 7桁 + ハイフン任意
// 例: 123-4567 / 1234567
private static readonly Regex PostalCodeRegex = new Regex(
@"^\d{3}-?\d{4}$",
RegexOptions.Compiled);
public static bool IsValid(string postalCode)
{
if (string.IsNullOrWhiteSpace(postalCode)) return false;
return PostalCodeRegex.IsMatch(postalCode);
}
// 正規化(ハイフンを統一)
public static string Normalize(string postalCode)
{
if (!IsValid(postalCode)) return null;
// ハイフンを取り除いて統一フォーマットに変換
var digits = postalCode.Replace("-", "");
return $"{digits.Substring(0, 3)}-{digits.Substring(3, 4)}";
}
}
// 使い方
class Program
{
static void Main()
{
Console.WriteLine(PostalCodeValidator.IsValid("123-4567")); // True
Console.WriteLine(PostalCodeValidator.IsValid("1234567")); // True
Console.WriteLine(PostalCodeValidator.IsValid("12-34567")); // False
Console.WriteLine(PostalCodeValidator.Normalize("1234567")); // "123-4567"
}
}
業務系では 入力の正規化(Normalize) をセットで実装するのが鉄則。ユーザー入力のブレ(ハイフン有無)を統一フォーマットに揃えて DB に保存することで、検索 / 表示が一貫します。
ハマりポイント。^\d{7}$ だけで書くと 1234567 のみマッチして 123-4567 が弾かれます。-? の任意ハイフンが業務系の現実版。
パターン4: 全角半角判定 — Unicode 範囲
全角文字混入チェックは業務系で頻出。Unicode 範囲指定で判定します。
using System.Text.RegularExpressions;
public static class CharTypeValidator
{
// ✅ 全角文字(ASCII 外)が含まれているか
private static readonly Regex FullWidthRegex = new Regex(
@"[^\x00-\x7F]",
RegexOptions.Compiled);
// ✅ 半角カナが含まれているか
private static readonly Regex HalfWidthKanaRegex = new Regex(
@"[。-゚]",
RegexOptions.Compiled);
// ✅ 日本語文字(ひらがな・カタカナ・漢字)のみ
private static readonly Regex JapaneseOnlyRegex = new Regex(
@"^[ぁ-んァ-ヶ一-龯]+$",
RegexOptions.Compiled);
public static bool ContainsFullWidth(string input)
{
if (string.IsNullOrEmpty(input)) return false;
return FullWidthRegex.IsMatch(input);
}
public static bool ContainsHalfWidthKana(string input)
{
if (string.IsNullOrEmpty(input)) return false;
return HalfWidthKanaRegex.IsMatch(input);
}
public static bool IsJapaneseOnly(string input)
{
if (string.IsNullOrEmpty(input)) return false;
return JapaneseOnlyRegex.IsMatch(input);
}
}
// 使い方
class Program
{
static void Main()
{
Console.WriteLine(CharTypeValidator.ContainsFullWidth("abc")); // False
Console.WriteLine(CharTypeValidator.ContainsFullWidth("abc日本語")); // True
Console.WriteLine(CharTypeValidator.ContainsFullWidth("ABCあ")); // True (全角A)
Console.WriteLine(CharTypeValidator.ContainsHalfWidthKana("アイウ")); // True
Console.WriteLine(CharTypeValidator.IsJapaneseOnly("こんにちは")); // True
Console.WriteLine(CharTypeValidator.IsJapaneseOnly("Hello")); // False
}
}
Unicode 範囲のポイント:
[^\x00-\x7F]— ASCII 範囲外(= 全角文字全般、最も汎用的)[。-゚]— 半角カナ(DB 移行時のチェックで頻出)[ぁ-んァ-ヶ一-龯]— ひらがな + カタカナ + 漢字(日本語のみ判定)
ハマりポイント。[-ヿ] だけで全角判定する と漢字(一-鿿)を見逃します。業務系で「全角混入チェック」する場合は ASCII 外を判定する [^\x00-\x7F] が汎用的で漏れがない。これ覚えておくと、漢字 / 全角記号 / 全角英数 / 全角空白すべてを一括判定できます。
業務系の CSV 取り込みで「半角想定のカラムに全角混入してた」事故は、ContainsFullWidth をセットで仕込むだけで防げます。
パターン5: 数値・日付フォーマット検証
業務系の入力検証で頻出する数値 / 日付フォーマット。
using System;
using System.Text.RegularExpressions;
public static class FormatValidator
{
// ✅ 整数(正負・桁数指定なし)
private static readonly Regex IntegerRegex = new Regex(
@"^-?\d+$",
RegexOptions.Compiled);
// ✅ 小数(正負・小数点任意)
private static readonly Regex DecimalRegex = new Regex(
@"^-?\d+(\.\d+)?$",
RegexOptions.Compiled);
// ✅ 日付 YYYY/MM/DD
private static readonly Regex DateRegex = new Regex(
@"^\d{4}/\d{2}/\d{2}$",
RegexOptions.Compiled);
public static bool IsInteger(string input)
{
return !string.IsNullOrWhiteSpace(input) && IntegerRegex.IsMatch(input);
}
public static bool IsDecimal(string input)
{
return !string.IsNullOrWhiteSpace(input) && DecimalRegex.IsMatch(input);
}
// 日付は Regex + DateTime.TryParse の二段検証
public static bool IsValidDate(string input)
{
if (string.IsNullOrWhiteSpace(input)) return false;
if (!DateRegex.IsMatch(input)) return false;
// フォーマット通過後、実在日付か検証(2026/02/30 を弾く)
return DateTime.TryParseExact(input, "yyyy/MM/dd",
null, System.Globalization.DateTimeStyles.None, out _);
}
}
// 使い方
class Program
{
static void Main()
{
Console.WriteLine(FormatValidator.IsInteger("123")); // True
Console.WriteLine(FormatValidator.IsInteger("-456")); // True
Console.WriteLine(FormatValidator.IsInteger("12.3")); // False
Console.WriteLine(FormatValidator.IsDecimal("3.14")); // True
Console.WriteLine(FormatValidator.IsDecimal("-2.5")); // True
Console.WriteLine(FormatValidator.IsValidDate("2026/05/15")); // True
Console.WriteLine(FormatValidator.IsValidDate("2026/02/30")); // False(実在しない)
}
}
日付検証は 正規表現 + DateTime.TryParseExact の二段検証 が業務系鉄則。正規表現だけだと 2026/02/30 のような実在しない日付を許可してしまうので、TryParseExact で実在チェックする組み合わせで対応します。
パターン6: RegexOptions.Compiled でパフォーマンス改善
業務系の大量入力検証では RegexOptions.Compiled が効きます。
using System.Diagnostics;
using System.Text.RegularExpressions;
public static class RegexPerformanceComparison
{
private const string Pattern = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";
// ❌ 都度コンパイル(遅い)
public static int CountValidNaive(string[] emails)
{
int count = 0;
foreach (var email in emails)
{
if (Regex.IsMatch(email, Pattern))
count++;
}
return count;
}
// ✅ Compiled で1回だけコンパイル(速い)
private static readonly Regex CompiledRegex = new Regex(
Pattern, RegexOptions.Compiled);
public static int CountValidCompiled(string[] emails)
{
int count = 0;
foreach (var email in emails)
{
if (CompiledRegex.IsMatch(email))
count++;
}
return count;
}
}
// 使い方
class Program
{
static void Main()
{
var emails = Enumerable.Range(0, 100000)
.Select(i => $"user{i}@example.com")
.ToArray();
var sw = Stopwatch.StartNew();
int c1 = RegexPerformanceComparison.CountValidNaive(emails);
sw.Stop();
Console.WriteLine($"Naive: {sw.ElapsedMilliseconds}ms (count={c1})");
sw.Restart();
int c2 = RegexPerformanceComparison.CountValidCompiled(emails);
sw.Stop();
Console.WriteLine($"Compiled: {sw.ElapsedMilliseconds}ms (count={c2})");
}
}
体感での速度差。10万件の IsMatch で:
| 方法 | 実行時間(体感) |
|---|---|
| 都度コンパイル(Naive) | 約 3秒 |
RegexOptions.Compiled 版 |
約 0.5秒 |
CSV 取り込み 10万行を検証するバッチ処理だと、この差が体感で大きく出ます。ただし 初回コンパイルのコストが上乗せ されるので、1回しか使わない正規表現には不向き。繰り返し使う正規表現は Compiled、1回限りなら素の Regex という判断軸を持つといい感じです。
俺の現場でも、CSV 取り込み処理が遅くて調べたら Regex が都度コンパイルされてた、というハマりがありました。RegexOptions.Compiled を入れたら処理時間が一気に短くなって、いい感じに高速化できました!!
ハマりポイント整理
業務系の正規表現で踏みやすいハマり:
| ハマりポイント | 原因 | 対処 |
|---|---|---|
| メール検証で正常メールが弾かれる | RFC5322 厳密版を使った | 簡易版に切り替え |
| 電話番号で国際電話が弾かれる | 国内専用パターン | 国際対応版を別用意 |
| 全角判定で漢字を見逃す | [-ヿ] 範囲指定の漏れ |
[^\x00-\x7F] で ASCII 外判定 |
IsMatch で部分一致してしまう |
^ $ 忘れ |
アンカー必須化 |
| バッチ処理が遅い | 都度コンパイル | RegexOptions.Compiled |
まとめ
C# 正規表現の業務系利用5パターン:
- メールアドレス —
^[^@\s]+@[^@\s]+\.[^@\s]+$の簡易版 - 電話番号 — 国内
^0\d{1,4}-?\d{1,4}-?\d{4}$/ 国際は別パターン - 郵便番号 —
^\d{3}-?\d{4}$+ Normalize で統一フォーマット - 全角半角判定 —
[^\x00-\x7F]で ASCII 外判定が汎用的 - 数値・日付 — 正規表現 + DateTime.TryParseExact の二段検証
業務系では 厳密版より現実版 を選ぶ判断軸が鉄則。ユーザー入力のブレを吸収する簡易版の方が、メンテ性と現場の事故防止の両面で勝ちます。大量呼び出しは RegexOptions.Compiled で高速化、^ $ アンカーは IsMatch で必須化、を覚えておけば業務系の入力検証はカバーできます。
よくある質問
Q1. C# でメールアドレス検証の正規表現はどう書く?
A. 業務系では ^[^@\s]+@[^@\s]+\.[^@\s]+$ 程度の簡易版が現実解です。RFC5322 厳密版(数百文字の正規表現)は正常なメールを弾くケースがあって、業務系の登録フォームでクレームの種になります。簡易版で @ の前後・ドット必須の3点だけチェックして、本格的な検証はメール送信時の到達確認に委ねるのが業務SE 鉄則です。
Q2. C# で電話番号検証で国内と国際を両方受け入れたい時は?
A. 正規表現を ^(\+?\d{1,4}[-\s]?)?\d{2,5}[-\s]?\d{2,5}[-\s]?\d{2,5}$ 程度にすれば国内 / 国際両方を受け入れられます。国内のみ厳密にしたい場合は ^0\d{1,4}-?\d{1,4}-?\d{4}$ で先頭 0 を強制。業務系で国際電話を受け付ける必要があるなら最初から国際対応にする、というのが業務SE 鉄則です。
Q3. C# で全角半角判定の正規表現はどう書く?
A. 全角文字の Unicode 範囲で判定します。日本語全般なら [ぁ-んァ-ヶ一-龯]、ASCII 外(全角記号含む)なら [^\x00-\x7F]。半角カナ判定は [。-゚]。業務系で「全角混入チェック」する場合は ASCII 外文字を検出する [^\x00-\x7F] が汎用的で、漢字・ひらがな・カタカナ・全角記号を一括判定できます。
Q4. C# Regex の RegexOptions.Compiled はいつ使う?
A. 同じ正規表現を大量呼び出しする時に使います。RegexOptions.Compiled を指定すると Regex オブジェクトを IL コンパイルして、2回目以降の IsMatch / Match が高速化されます。10万件の IsMatch で素の Regex が 3秒かかるところ、Compiled で 0.5秒程度(業務系体感)。初回コンパイルのコストが上乗せされるので、1回しか使わない正規表現には不向きです。
Q5. 正規表現の ^ $ を忘れるとどうなる?
A. 部分一致でマッチするので、想定外の文字列を許可してしまいます。例: \d{3}-\d{4} で郵便番号検証すると、abc123-4567xyz も「123-4567」が部分一致してマッチします。^\d{3}-\d{4}$ のように先頭末尾アンカーを付けると完全一致になって、入力検証として機能します。業務系の IsMatch は ^ $ 必須、と覚えておくと事故防げます。
関連記事
- C# 文字列結合のパフォーマンス — string + / StringBuilder / string.Concat の使い分け — 文字列処理パフォーマンスの隣接トピック
- C# 例外処理のベストプラクティス — 業務SE が try-catch-finally と IDisposable で詰まる時の整理 — IsMatch 例外処理の隣接トピック
- C# LINQ の Null ハンドリングとパフォーマンス — Any / Where / FirstOrDefault で詰まる時の整理 — パフォーマンス比較の隣接トピック
以上!
同じ罠でハマってる業務SE仲間いたら、どんどんシェア待ってるぜ!!


コメント