C#を用いたシステム開発において、ユーザーからの入力値や外部システムから受け取ったデータが「数値であるかどうか」を確認する作業は、プログラムの堅牢性を高めるために欠かせないステップです。

不適切な文字列を数値として処理しようとすると、実行時エラー(例外)が発生し、システム停止の原因となることもあります。

本記事では、初心者からシニアエンジニアまでが現場で即座に活用できる、C#における数値チェックの最適な実装パターンを網羅的に解説します。

数値チェックの基本:TryParseメソッド

C#で数値判定を行う際、最も標準的かつ推奨される方法が TryParse メソッドです。

このメソッドは、指定した文字列が数値に変換可能かどうかを判定し、変換可能な場合はその結果を返します。

TryParseが推奨される理由

以前のC#や他の言語では、Parse メソッドを try-catch ブロックで囲んで例外を捕捉する方法が使われていました。

しかし、例外(Exception)の発生は非常にコストが高い処理であり、大量のデータ処理を行う際にパフォーマンスを著しく低下させます。

TryParse は例外を発生させずに bool 値を返すため、パフォーマンスとコードの可読性の両面で優れています

実装例:int.TryParseによる整数判定

以下は、最も一般的な整数(int型)のチェック例です。

C# 7.0以降で導入された「out変数宣言」を活用することで、コードを簡潔に記述できます。

C#
using System;

public class Program
{
    public static void Main()
    {
        string input = "12345";

        // インラインで変数を宣言しつつ、数値チェックを実施
        if (int.TryParse(input, out int result))
        {
            // 数値として正しい場合の処理
            Console.WriteLine($"変換成功: {result}");
        }
        else
        {
            // 数値ではない場合の処理
            Console.WriteLine("エラー: 入力値は有効な整数ではありません。");
        }
    }
}
実行結果
変換成功: 12345

浮動小数点数(double, decimal)のチェック

小数を含む数値を判定する場合も、同様に double.TryParsedecimal.TryParse を使用します。

特に、通貨計算や厳密な数値計算が必要な場合は decimal 型を使用するのが鉄則です。

C#
string priceInput = "1500.50";

if (decimal.TryParse(priceInput, out decimal price))
{
    Console.WriteLine($"有効な価格です: {price:C}");
}
else
{
    Console.WriteLine("無効な価格形式です。");
}
実行結果
有効な価格です: ¥1,500.50

正規表現(Regex)を用いた高度な数値判定

TryParse は「型として正しいか」を判定しますが、ビジネスロジックにおける「特定のフォーマットに従っているか」を判定するには不十分な場合があります。

例えば、「5桁の数字のみ許可する」といった制約がある場合は、正規表現(Regular Expressions)の活用が適しています。

郵便番号やIDのフォーマットチェック

正規表現を使用することで、数値であることに加え、桁数や符号の有無を細かく制御できます。

C#
using System;
using System.Text.RegularExpressions;

public class RegexValidator
{
    public static void Main()
    {
        string input = "0901234";
        
        // 7桁の半角数字のみを許可する正規表現
        string pattern = @"^[0-9]{7}$";

        if (Regex.IsMatch(input, pattern))
        {
            Console.WriteLine("有効なID形式です。");
        }
        else
        {
            Console.WriteLine("IDは7桁の数字で入力してください。");
        }
    }
}
実行結果
有効なID形式です。

正規表現を使用する際の注意点

正規表現は強力ですが、複雑なパターンを多用すると処理速度が低下する可能性があります。

また、Regex クラスを使用する際は、インスタンスを再利用するか、静的メソッドのキャッシュ機能を活用することが推奨されます。

LINQとCharメソッドを用いた簡易チェック

「すべての文字が数字であるか」を確認したいだけであれば、LINQの All メソッドと char.IsDigit を組み合わせる方法も有効です。

全ての文字が数字かどうかを判定する

この方法は、負の符号(-)や小数点(.)が含まれている場合に false を返すため、正の整数のみを対象とする場合に非常にシンプルに記述できます。

C#
using System;
using System.Linq;

public class LinqCheck
{
    public static void Main()
    {
        string input = "202410";

        // 全ての文字が'0'-'9'の範囲内かチェック
        bool isDigitOnly = input.All(char.IsDigit);

        if (isDigitOnly && !string.IsNullOrEmpty(input))
        {
            Console.WriteLine("すべての文字が数字です。");
        }
        else
        {
            Console.WriteLine("数字以外の文字が含まれているか、空文字です。");
        }
    }
}
実行結果
すべての文字が数字です。

最新のC#によるパターンマッチングの活用

近年のC#(C# 9.0以降)では、パターンマッチングが強化され、より直感的な数値チェックが可能になっています。

特に、オブジェクト型の変数が特定の型として扱えるかを判定する際に威力を発揮します。

is演算子による型判定

C#
public void ProcessData(object data)
{
    // dataがint型であればそのまま数値として取り出す
    if (data is int number)
    {
        Console.WriteLine($"整数を受け取りました: {number}");
    }
    else if (data is string str && int.TryParse(str, out int parsed))
    {
        Console.WriteLine($"文字列から変換された整数: {parsed}");
    }
}

このように、「型判定」と「変数への代入」を一行で行えるため、コードのネストを浅く保つことができます。

空文字・Null・空白のハンドリング

数値チェックを行う際、意外と忘れがちなのが null や空文字の処理です。

int.TryParsenull を渡しても例外は発生せず false を返しますが、ビジネスルールとして「未入力」と「不正な入力」を区別したい場合には明示的なチェックが必要です。

推奨される事前チェック

C#
public bool IsValidInput(string input)
{
    if (string.IsNullOrWhiteSpace(input))
    {
        // 未入力または空白のみの場合
        return false;
    }

    return int.TryParse(input, out _);
}

string.IsNullOrWhiteSpace を使用することで、スペースのみが入力されたケースも確実に除外できます。

数値の範囲チェック(バリデーション)

「数値であること」が確認できた後は、その値が「許容範囲内にあるか」を確認する必要があります。

例えば、年齢の入力であれば 0 から 150 の間であるべきです。

範囲指定のスマートな実装

C#の比較演算子を使用するのが一般的ですが、C# 9.0以降のパターンマッチングを使うと、より英語の文章に近い形式で記述できます。

C#
if (int.TryParse(input, out int age))
{
    // C# 9.0以降の論理パターン
    if (age is >= 0 and <= 150)
    {
        Console.WriteLine("有効な年齢です。");
    }
    else
    {
        Console.WriteLine("年齢が範囲外です。");
    }
}

この記述方法は、境界値条件が視覚的に分かりやすいため、メンテナンス性が向上します。

高度なシナリオ:16進数や指数のチェック

一般的な10進数以外の数値形式を扱う必要がある場合、NumberStyles 列挙型を使用することで TryParse の挙動をカスタマイズできます。

16進数の判定例

C#
using System.Globalization;

string hexInput = "0xAF";
// プレフィックス(0x)を考慮して16進数としてパース
if (int.TryParse(hexInput.Replace("0x", ""), NumberStyles.HexNumber, null, out int hexValue))
{
    Console.WriteLine($"16進数として認識: {hexValue}");
}

このように、System.Globalization を活用することで、カンマ区切りの数値や通貨記号付きの文字列なども柔軟に判定可能です。

パフォーマンスの最適化:ReadOnlySpan<char>

大量のテキストデータをパースする場合、文字列を切り出すたびに新しい文字列インスタンスが作成され、メモリ(ヒープ)を圧迫することがあります。

最新のC#では、ReadOnlySpan<char> を利用することで、メモリ割り当てを抑えた高速な数値判定が可能です。

C#
public bool FastNumberCheck(string largeText)
{
    ReadOnlySpan<char> span = largeText.AsSpan();
    // 最初の5文字だけをチェックしたい場合なども、メモリコピーなしでスライス可能
    ReadOnlySpan<char> subSpan = span.Slice(0, 5);
    
    return int.TryParse(subSpan, out _);
}

この手法は、ログファイルの解析やリアルタイム通信のパケット解析など、パフォーマンスが極めて重要なシステムで非常に効果的です。

ASP.NET Coreにおける数値チェック

Webアプリケーション(ASP.NET Core)を開発している場合、コントローラーにデータが到達する前に、Data Annotations(データ注釈属性)を用いて宣言的に数値チェックを行うのがベストプラクティスです。

モデルクラスでの定義

C#
using System.ComponentModel.DataAnnotations;

public class UserRegistrationModel
{
    [Required(ErrorMessage = "年齢は必須項目です。")]
    [Range(18, 99, ErrorMessage = "18歳から99歳の間で入力してください。")]
    public int Age { get; set; }

    [RegularExpression(@"^[0-9]{3}-[0-9]{4}$", ErrorMessage = "郵便番号の形式が正しくありません。")]
    public string ZipCode { get; set; }
}

このように属性を付与することで、フレームワークが自動的にバリデーションを実行し、開発者が個別に TryParse を記述する手間を省けます。

実装パターンの比較表

状況に応じて最適な手法を選択できるよう、各手法の特徴をまとめました。

手法適したユースケースメリットデメリット
TryParse一般的な数値判定高速、標準的、安全フォーマットの細かな指定は不可
Regex複雑な書式・桁数指定柔軟性が非常に高い処理が重い、可読性が下がる
LINQ (All)正の整数のみの簡易チェック記述がシンプル負数や小数に対応できない
Pattern Matching型が不明なオブジェクトの処理モダンで読みやすいC#のバージョンに依存する
Data AnnotationsWeb API / MVCの入力値宣言的で管理しやすい属性で表現できる範囲に限られる

まとめ

C#で数値チェック(数値判定)を行う際は、基本的には TryParse メソッドを選択し、out変数宣言を活用するのが最も効率的で安全な方法です。

  • 単純な変換可否を知りたいなら: int.TryParse または double.TryParse
  • 桁数や特定の形式を厳密に定義したいなら: Regex
  • パフォーマンスを極限まで追求するなら: Span<char>
  • Webアプリの入力バリデーションなら: Data Annotations

これらを用途に応じて使い分けることで、バグが少なく、かつ保守性の高いプログラムを記述することができます。

特に、入力値が期待通りの型であるかを早期にチェックする「ガード節」の考え方を取り入れ、例外を未然に防ぐ実装を心がけましょう。