文字列

【C#】TryParseで文字列を安全に数値変換する方法と失敗時のハンドリング

文字列を数値に安全に変換したいなら、int.TryParseなどのTryParse系が最適です。

bool値で成功可否を受け取り、成功時はout引数に値が入り、失敗しても例外が出ず処理を続行できます。

intだけでなくdoubleやdecimalなど多様な型で同じ構文を使えます。

TryParse の基本

C#で文字列を数値に変換する際、TryParseメソッドは非常に便利で安全な方法です。

ここでは、TryParseの基本的な特徴や、従来のParseメソッドとの違いについて詳しく解説します。

TryParse と Parse の違い

Parseメソッドは、文字列を数値型に変換する際に使われる代表的なメソッドです。

例えば、int.Parseは文字列を整数に変換しますが、変換に失敗すると例外FormatExceptionOverflowExceptionが発生します。

一方、TryParseは変換が成功したかどうかをbool型で返し、失敗しても例外を発生させません。

これにより、例外処理のコストを抑えつつ安全に変換を試みることができます。

以下のサンプルコードは、ParseTryParseの違いを示しています。

using System;
class Program
{
    static void Main()
    {
        string input = "123abc";
        // Parseを使った場合(例外が発生する可能性あり)
        try
        {
            int number = int.Parse(input);
            Console.WriteLine($"Parse成功: {number}");
        }
        catch (FormatException)
        {
            Console.WriteLine("Parse失敗: 文字列が数値ではありません");
        }
        // TryParseを使った場合(例外は発生しない)
        bool success = int.TryParse(input, out int result);
        if (success)
        {
            Console.WriteLine($"TryParse成功: {result}");
        }
        else
        {
            Console.WriteLine("TryParse失敗: 変換できませんでした");
        }
    }
}
Parse失敗: 文字列が数値ではありません
TryParse失敗: 変換できませんでした

このように、TryParseは例外処理を使わずに変換の成否を判定できるため、パフォーマンス面でも優れています。

例外を出さない設計

TryParseの最大の特徴は、変換失敗時に例外を発生させないことです。

例外は処理コストが高く、頻繁に発生するとパフォーマンスに悪影響を与えます。

TryParseはこの問題を回避し、より効率的に文字列の数値変換を行います。

bool 戻り値と out パラメータ

TryParseは戻り値としてbool型を返します。

これは変換が成功したかどうかを示すフラグです。

変換に成功した場合はtrue、失敗した場合はfalseとなります。

また、変換後の値はoutパラメータで返されます。

outパラメータはメソッドの呼び出し元に値を返すための仕組みで、TryParseでは変換結果の数値がここに格納されます。

以下のコードは、TryParseの典型的な使い方を示しています。

using System;
class Program
{
    static void Main()
    {
        string input = "456";
        bool success = int.TryParse(input, out int number);
        if (success)
        {
            Console.WriteLine($"変換成功: {number}");
        }
        else
        {
            Console.WriteLine("変換失敗");
        }
    }
}
変換成功: 456

この例では、inputが整数に変換可能な文字列なので、successtrueとなり、numberに変換後の値が格納されます。

既定値の扱い

TryParseが失敗した場合、outパラメータにはその型の既定値が設定されます。

例えば、int.TryParseで失敗するとoutパラメータには0が入ります。

これは、int型の既定値が0であるためです。

以下の例で確認してみましょう。

using System;
class Program
{
    static void Main()
    {
        string input = "abc"; // 数値に変換できない文字列
        bool success = int.TryParse(input, out int number);
        Console.WriteLine($"変換成功フラグ: {success}");
        Console.WriteLine($"変換結果の値: {number}");
    }
}
変換成功フラグ: False
変換結果の値: 0

このように、変換に失敗しても例外は発生せず、numberには0が設定されます。

これを踏まえて、失敗時の処理を適切に設計することが重要です。

例えば、0が有効な入力値としてあり得る場合は、successの値を必ずチェックしてからnumberを利用するようにしてください。

そうしないと、誤った値を使ってしまうリスクがあります。

以上がTryParseの基本的な使い方と特徴です。

Parseとの違いや例外を出さない設計、bool戻り値とoutパラメータの役割、失敗時の既定値の扱いを理解することで、安全かつ効率的に文字列から数値への変換を行えます。

主な数値型と TryParse

int.TryParse の使いどころ

int.TryParseは、文字列を32ビット符号付き整数intに変換する際に使います。

整数の範囲は-2,147,483,648から2,147,483,647までで、この範囲内の数値であれば変換が成功します。

ユーザー入力やファイルから読み込んだ文字列を整数に変換する際に最もよく使われるメソッドの一つです。

最大値・最小値の制約

int.TryParseは、変換対象の文字列がintの範囲外の場合、変換に失敗します。

例えば、”2147483648″(int.MaxValueの1つ大きい値)や”-2147483649″(int.MinValueの1つ小さい値)は変換できません。

以下のコードで確認してみましょう。

using System;
class Program
{
    static void Main()
    {
        string tooLarge = "2147483648"; // int.MaxValue + 1
        string tooSmall = "-2147483649"; // int.MinValue - 1
        bool successLarge = int.TryParse(tooLarge, out int resultLarge);
        bool successSmall = int.TryParse(tooSmall, out int resultSmall);
        Console.WriteLine($"\"{tooLarge}\" の変換成功: {successLarge}, 結果: {resultLarge}");
        Console.WriteLine($"\"{tooSmall}\" の変換成功: {successSmall}, 結果: {resultSmall}");
    }
}
"2147483648" の変換成功: False, 結果: 0
"-2147483649" の変換成功: False, 結果: 0

このように、範囲外の値は変換に失敗し、outパラメータには0が設定されます。

範囲チェックが必要な場合は、TryParseの戻り値を必ず確認してください。

先頭ゼロの扱い

int.TryParseは、先頭にゼロが付いた文字列も正常に変換します。

例えば、”007″は整数の7として扱われます。

先頭のゼロは無視されるため、特別な処理は不要です。

using System;
class Program
{
    static void Main()
    {
        string input = "007";
        bool success = int.TryParse(input, out int number);
        Console.WriteLine($"変換成功: {success}, 結果: {number}");
    }
}
変換成功: True, 結果: 7

ただし、先頭に空白や符号がある場合は、それらも適切に処理されます。

例えば、”+007″や”-007″も正しく変換されます。

double.TryParse の特徴

double.TryParseは、倍精度浮動小数点数doubleへの変換に使います。

整数だけでなく、小数点を含む数値や指数表記も扱えます。

小数点記号とロケール

double.TryParseは、実行環境のカルチャ(ロケール)に依存して小数点記号を解釈します。

日本や米国の環境では小数点は「.」ですが、ドイツやフランスなど一部の国では「,」が小数点として使われます。

以下の例は、カルチャを指定せずにdouble.TryParseを使った場合の挙動です。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string inputDot = "123.45";
        string inputComma = "123,45";
        bool successDot = double.TryParse(inputDot, out double resultDot);
        bool successComma = double.TryParse(inputComma, out double resultComma);
        Console.WriteLine($"\"{inputDot}\" の変換成功: {successDot}, 結果: {resultDot}");
        Console.WriteLine($"\"{inputComma}\" の変換成功: {successComma}, 結果: {resultComma}");
    }
}
"123.45" の変換成功: True, 結果: 123.45
"123,45" の変換成功: False, 結果: 0

この例では、環境が小数点を「.」としているため、「123.45」は成功しますが、「123,45」は失敗します。

逆に、カルチャを指定して変換することも可能です。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "123,45";
        CultureInfo culture = new CultureInfo("fr-FR"); // フランスのカルチャ(小数点はカンマ)
        bool success = double.TryParse(input, NumberStyles.Float, culture, out double result);
        Console.WriteLine($"\"{input}\" の変換成功: {success}, 結果: {result}");
    }
}
"123,45" の変換成功: True, 結果: 123.45

このように、TryParseNumberStylesIFormatProviderを指定することで、カルチャ依存の数値表現にも対応できます。

無限大・NaN の扱い

double.TryParseは、文字列が"Infinity""-Infinity""NaN"の場合も変換に成功します。

これらは特別な浮動小数点数の値を表します。

using System;
class Program
{
    static void Main()
    {
        string[] inputs = { "Infinity", "-Infinity", "NaN" };
        foreach (var input in inputs)
        {
            bool success = double.TryParse(input, out double result);
            Console.WriteLine($"\"{input}\" の変換成功: {success}, 結果: {result}");
        }
    }
}
"Infinity" の変換成功: True, 結果: Infinity
"-Infinity" の変換成功: True, 結果: -Infinity
"NaN" の変換成功: True, 結果: NaN

これらの値は計算上特別な意味を持つため、扱う際は注意が必要です。

decimal.TryParse と金融計算

decimal.TryParseは、金融計算や高精度な小数計算に適したdecimal型への変換に使います。

decimalは浮動小数点数よりも精度が高く、丸め誤差が少ないため、通貨計算などでよく利用されます。

精度と丸め誤差

decimalは28~29桁の有効数字を持ち、浮動小数点のdoubleよりも精度が高いです。

decimal.TryParseは文字列をdecimalに変換し、精度を保ったまま扱えます。

以下の例で、decimaldoubleの違いを比較します。

using System;
class Program
{
    static void Main()
    {
        string input = "0.1";
        bool successDecimal = decimal.TryParse(input, out decimal decValue);
        bool successDouble = double.TryParse(input, out double dblValue);
        Console.WriteLine($"decimal: {decValue}");
        Console.WriteLine($"double: {dblValue}");
        // 0.1を10回足す計算
        decimal decSum = 0;
        double dblSum = 0;
        for (int i = 0; i < 10; i++)
        {
            decSum += decValue;
            dblSum += dblValue;
        }
        Console.WriteLine($"decimal 0.1を10回足した結果: {decSum}");
        Console.WriteLine($"double 0.1を10回足した結果: {dblSum}");
    }
}
decimal: 0.1
double: 0.1
decimal 0.1を10回足した結果: 1.0
double 0.1を10回足した結果: 0.9999999999999999

このように、decimalは丸め誤差が少なく、金融計算に適しています。

decimal.TryParseを使うことで、文字列から高精度な数値を安全に取得できます。

long, short, byte などその他の型

TryParseintdoubledecimal以外にも多くの数値型で利用可能です。

代表的なものを以下に示します。

型名サイズ(ビット)範囲用途例
long64-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807大きな整数の扱い
short16-32,768 ~ 32,767小さな整数の扱い
byte80 ~ 255バイナリデータや小さな正の整数

それぞれの型に対してTryParseメソッドが用意されており、使い方はint.TryParseとほぼ同じです。

using System;
class Program
{
    static void Main()
    {
        string longInput = "9223372036854775807"; // long.MaxValue
        string shortInput = "32767";              // short.MaxValue
        string byteInput = "255";                  // byte.MaxValue
        bool successLong = long.TryParse(longInput, out long longResult);
        bool successShort = short.TryParse(shortInput, out short shortResult);
        bool successByte = byte.TryParse(byteInput, out byte byteResult);
        Console.WriteLine($"long変換成功: {successLong}, 結果: {longResult}");
        Console.WriteLine($"short変換成功: {successShort}, 結果: {shortResult}");
        Console.WriteLine($"byte変換成功: {successByte}, 結果: {byteResult}");
    }
}
long変換成功: True, 結果: 9223372036854775807
short変換成功: True, 結果: 32767
byte変換成功: True, 結果: 255

これらの型も範囲外の値や不正な文字列の場合は変換に失敗し、falseが返ります。

用途に応じて適切な型を選び、TryParseで安全に変換してください。

文化依存の変換

CultureInfo を指定する方法

数値の文字列変換は、文化(カルチャ)によって表記方法が異なるため、TryParseで変換する際にCultureInfoを指定することが重要です。

CultureInfoを指定することで、桁区切りや小数点記号、通貨記号などの文化特有の表現を正しく解釈できます。

カンマ区切りの桁区切り符

多くの文化圏では、数値の桁区切りにカンマ,やピリオド.を使います。

例えば、英語圏では「1,000」は千の区切りを示しますが、ドイツ語圏では「1.000」が同じ意味です。

double.TryParsedecimal.TryParseで桁区切りを含む文字列を変換する場合、CultureInfoを指定しないと失敗することがあります。

以下の例で確認しましょう。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "1,234.56"; // 英語圏の表記
        CultureInfo enUS = new CultureInfo("en-US");
        CultureInfo deDE = new CultureInfo("de-DE");
        bool successEn = double.TryParse(input, NumberStyles.Number, enUS, out double resultEn);
        bool successDe = double.TryParse(input, NumberStyles.Number, deDE, out double resultDe);
        Console.WriteLine($"en-US での変換成功: {successEn}, 結果: {resultEn}");
        Console.WriteLine($"de-DE での変換成功: {successDe}, 結果: {resultDe}");
    }
}
en-US での変換成功: True, 結果: 1234.56
de-DE での変換成功: False, 結果: 0

この例では、en-USカルチャでは「1,234.56」が正しく変換されますが、de-DEカルチャではカンマが小数点として扱われるため失敗します。

逆に、ドイツ語圏の表記「1.234,56」を変換する場合は以下のようになります。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "1.234,56"; // ドイツ語圏の表記
        CultureInfo deDE = new CultureInfo("de-DE");
        bool success = double.TryParse(input, NumberStyles.Number, deDE, out double result);
        Console.WriteLine($"de-DE での変換成功: {success}, 結果: {result}");
    }
}
de-DE での変換成功: True, 結果: 1234.56

このように、CultureInfoを適切に指定することで、文化依存の桁区切りや小数点を正しく処理できます。

アラビア数字以外の入力

TryParseは、CultureInfoに基づいて数字の文字セットも解釈します。

例えば、アラビア数字(0~9)以外に、東アラビア数字やデーヴァナーガリー数字などの文化特有の数字も扱えます。

以下は、東アラビア数字を含む文字列をTryParseで変換する例です。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        // 東アラビア数字で「1234」
        string easternArabicDigits = "\u0661\u0662\u0663\u0664";
        CultureInfo arSA = new CultureInfo("ar-SA");
        bool success = int.TryParse(easternArabicDigits, NumberStyles.Integer, arSA, out int result);
        Console.WriteLine($"変換成功: {success}, 結果: {result}");
    }
}
変換成功: True, 結果: 1234

このように、CultureInfoを指定することで、アラビア数字以外の数字も正しく変換できます。

文化ごとの数字表現に対応したい場合は、必ず適切なCultureInfoを指定してください。

NumberStyles で制限を付ける

TryParseのオーバーロードには、NumberStylesという列挙型を指定できるものがあります。

NumberStylesを使うと、変換時に許可する数値の書式を細かく制御できます。

これにより、入力の妥当性を厳密にチェックしたり、特定の形式だけを受け入れたりできます。

AllowLeadingSign

AllowLeadingSignは、数値の先頭にプラス+やマイナス-の符号を許可するオプションです。

これを指定しないと、符号付きの文字列は変換に失敗します。

以下の例では、符号付きの文字列をAllowLeadingSignあり・なしで比較しています。

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        string input = "-123";
        NumberStyles styleWithSign = NumberStyles.Integer | NumberStyles.AllowLeadingSign;
        // NumberStyles.Integer から AllowLeadingSign を除外して符号なしにする
        NumberStyles styleWithoutSign = NumberStyles.Integer & ~NumberStyles.AllowLeadingSign;

        bool successWithSign = int.TryParse(input, styleWithSign, CultureInfo.InvariantCulture, out int resultWithSign);
        bool successWithoutSign = int.TryParse(input, styleWithoutSign, CultureInfo.InvariantCulture, out int resultWithoutSign);

        Console.WriteLine($"AllowLeadingSignあり: 成功={successWithSign}, 結果={resultWithSign}");
        Console.WriteLine($"AllowLeadingSignなし: 成功={successWithoutSign}, 結果={resultWithoutSign}");
    }
}
AllowLeadingSignあり: 成功=True, 結果=-123
AllowLeadingSignなし: 成功=False, 結果=0

符号を含む文字列を扱う場合は、AllowLeadingSignを必ず指定しましょう。

AllowCurrencySymbol

AllowCurrencySymbolは、通貨記号(例:$¥)を許可するオプションです。

これを指定すると、通貨記号を含む文字列も変換可能になります。

以下の例では、通貨記号付きの文字列を変換しています。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "$1234.56";
        NumberStyles style = NumberStyles.Number | NumberStyles.AllowCurrencySymbol;
        CultureInfo culture = new CultureInfo("en-US");
        bool success = decimal.TryParse(input, style, culture, out decimal result);
        Console.WriteLine($"通貨記号ありの変換成功: {success}, 結果: {result}");
    }
}
通貨記号ありの変換成功: True, 結果: 1234.56

通貨記号を含む入力を受け付ける場合は、AllowCurrencySymbolを指定してください。

AllowHexSpecifier

AllowHexSpecifierは、16進数表記の文字列を変換するためのオプションです。

これを指定すると、TryParseは文字列を16進数として解釈します。

以下の例は、16進数文字列をintに変換しています。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string hexInput = "FF";
        NumberStyles style = NumberStyles.AllowHexSpecifier;
        bool success = int.TryParse(hexInput, style, CultureInfo.InvariantCulture, out int result);
        Console.WriteLine($"16進数変換成功: {success}, 結果: {result}");
    }
}
16進数変換成功: True, 結果: 255

16進数表記を扱う場合は、AllowHexSpecifierを指定し、CultureInfo.InvariantCultureを使うのが一般的です。

なお、16進数変換時は符号や小数点は扱えません。

これらのCultureInfoNumberStylesの組み合わせを活用することで、文化依存の数値表現や特殊な書式を安全かつ柔軟に処理できます。

入力の多様性に対応する際は、これらのオプションを適切に設定してください。

失敗時のハンドリング戦略

既定値を返す分岐

TryParseは変換に失敗した場合、例外を発生させずにfalseを返します。

そのため、失敗時の処理は呼び出し側で明示的に行う必要があります。

最もシンプルな方法は、失敗時に既定値を返す分岐を設けることです。

例えば、ユーザー入力を整数に変換し、失敗したら0を使うケースを考えます。

using System;
class Program
{
    static void Main()
    {
        string input = "abc"; // 数値に変換できない文字列
        int number;
        if (int.TryParse(input, out number))
        {
            Console.WriteLine($"変換成功: {number}");
        }
        else
        {
            number = 0; // 既定値を設定
            Console.WriteLine($"変換失敗のため既定値を使用: {number}");
        }
    }
}
変換失敗のため既定値を使用: 0

この方法は簡単で直感的ですが、0が有効な入力値としてあり得る場合は注意が必要です。

誤って失敗を見逃すリスクがあるため、失敗時の既定値を設定する際は、ドメインの仕様に合致しているか確認してください。

また、既定値を返す処理はメソッド化して再利用すると便利です。

static int ParseOrDefault(string input, int defaultValue = 0)
{
    return int.TryParse(input, out int result) ? result : defaultValue;
}

例外に変換して投げ直す

TryParseは例外を発生させませんが、場合によっては変換失敗を例外として扱いたいこともあります。

特に、変換失敗が致命的なエラーである場合や、呼び出し元で例外処理を一元管理したい場合に有効です。

以下の例は、TryParseの結果をチェックし、失敗したらFormatExceptionを投げ直す方法です。

using System;
class Program
{
    static int ParseOrThrow(string input)
    {
        if (int.TryParse(input, out int result))
        {
            return result;
        }
        else
        {
            throw new FormatException($"入力文字列 '{input}' は整数に変換できません。");
        }
    }
    static void Main()
    {
        string input = "abc";
        try
        {
            int number = ParseOrThrow(input);
            Console.WriteLine($"変換成功: {number}");
        }
        catch (FormatException ex)
        {
            Console.WriteLine($"例外発生: {ex.Message}");
        }
    }
}
例外発生: 入力文字列 'abc' は整数に変換できません。

この方法は、例外処理の仕組みを活用してエラーを明示的に伝えられるため、堅牢な設計に役立ちます。

ただし、例外処理はコストが高いため、頻繁に発生する可能性がある場合は避けるべきです。

ログとユーザー通知の分離

変換失敗時の対応として、ログ記録とユーザーへの通知は役割を分けて実装することが望ましいです。

ログは開発者や運用担当者向けの情報であり、ユーザー通知は利用者にわかりやすく伝えるためのものです。

例えば、変換失敗をログに記録しつつ、ユーザーにはシンプルなエラーメッセージを表示するケースを示します。

using System;
class Program
{
    static void LogError(string message)
    {
        // 実際はファイルや監視システムに出力する想定
        Console.WriteLine($"[ログ] {DateTime.Now}: {message}");
    }
    static void Main()
    {
        string input = "xyz";
        if (int.TryParse(input, out int number))
        {
            Console.WriteLine($"変換成功: {number}");
        }
        else
        {
            LogError($"数値変換失敗: 入力値='{input}'");
            Console.WriteLine("入力が正しい数値ではありません。再度入力してください。");
        }
    }
}
[ログ] 2024/06/01 12:00:00: 数値変換失敗: 入力値='xyz'
入力が正しい数値ではありません。再度入力してください。

このように、ログは詳細な情報を記録し、ユーザーには必要最低限のメッセージを表示することで、セキュリティやユーザビリティを両立できます。

また、ログ出力の仕組みはILoggerインターフェースなどを使って抽象化し、将来的な拡張やテストを容易にすることもおすすめです。

入力検証のパターン

フォーム入力と TryParse

ユーザーが入力するフォームの値は、必ずしも期待通りの数値とは限りません。

TryParseを使うことで、入力値が数値として有効かどうかを安全に検証できます。

フォーム入力の検証は、ユーザー体験を向上させるために重要なステップです。

UI での事前バリデーション

フォームのUI側で入力値の妥当性をチェックすることは、ユーザーに即時フィードバックを与え、誤入力を減らす効果があります。

例えば、テキストボックスに数値のみを許可する制限を設けたり、入力完了時にTryParseで変換を試みてエラーメッセージを表示したりします。

以下は、C#のWindowsフォームやWPFでの数値入力検証の例です。

ここでは、テキストボックスの内容をint.TryParseで検証し、無効な場合はエラーメッセージを表示します。

using System;
using System.Windows.Forms;
public class NumberInputForm : Form
{
    private TextBox inputTextBox;
    private Label messageLabel;
    private Button submitButton;
    public NumberInputForm()
    {
        inputTextBox = new TextBox { Location = new System.Drawing.Point(20, 20), Width = 200 };
        messageLabel = new Label { Location = new System.Drawing.Point(20, 50), Width = 300 };
        submitButton = new Button { Text = "送信", Location = new System.Drawing.Point(20, 80) };
        submitButton.Click += SubmitButton_Click;
        Controls.Add(inputTextBox);
        Controls.Add(messageLabel);
        Controls.Add(submitButton);
    }
    private void SubmitButton_Click(object sender, EventArgs e)
    {
        if (int.TryParse(inputTextBox.Text, out int number))
        {
            messageLabel.Text = $"入力値は有効な整数です: {number}";
        }
        else
        {
            messageLabel.Text = "無効な入力です。整数を入力してください。";
        }
    }
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new NumberInputForm());
    }
}

この例では、ユーザーが「送信」ボタンを押したときにTryParseで入力値を検証し、結果に応じてメッセージを表示しています。

UIでの事前バリデーションは、ユーザーの誤入力を減らし、スムーズな操作を促します。

コマンドライン引数の解析

C#のコンソールアプリケーションでは、コマンドライン引数として文字列が渡されます。

これらの引数を数値に変換する際にもTryParseが活躍します。

引数は外部からの入力であるため、必ず検証を行い、無効な値に対して適切に対応する必要があります。

以下は、コマンドライン引数を整数に変換し、変換に失敗した場合はエラーメッセージを表示する例です。

using System;
class Program
{
    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("引数を指定してください。");
            return;
        }
        foreach (var arg in args)
        {
            if (int.TryParse(arg, out int number))
            {
                Console.WriteLine($"引数 '{arg}' は整数に変換されました: {number}");
            }
            else
            {
                Console.WriteLine($"引数 '{arg}' は整数に変換できません。");
            }
        }
    }
}
// 実行例: dotnet run 123 abc 456
引数 '123' は整数に変換されました: 123
引数 'abc' は整数に変換できません。
引数 '456' は整数に変換されました: 456

このように、コマンドライン引数の解析ではTryParseを使って安全に数値変換を行い、無効な入力に対しては適切なメッセージを出すことが重要です。

JSON や XML の値チェック

JSONやXMLなどのデータフォーマットから数値を読み取る場合も、文字列として取得した値をTryParseで検証することが多いです。

外部データは信頼できないことが多いため、変換失敗に備えた堅牢な処理が求められます。

以下は、JSON文字列から数値を取り出し、TryParseで検証する例です。

ここではSystem.Text.Jsonを使っています。

using System;
using System.Text.Json;
class Program
{
    static void Main()
    {
        string json = @"{ ""age"": ""30"", ""height"": ""abc"" }";
        using (JsonDocument doc = JsonDocument.Parse(json))
        {
            JsonElement root = doc.RootElement;
            if (root.TryGetProperty("age", out JsonElement ageElement))
            {
                string ageStr = ageElement.GetString();
                if (int.TryParse(ageStr, out int age))
                {
                    Console.WriteLine($"年齢: {age}");
                }
                else
                {
                    Console.WriteLine("年齢の値が不正です。");
                }
            }
            if (root.TryGetProperty("height", out JsonElement heightElement))
            {
                string heightStr = heightElement.GetString();
                if (int.TryParse(heightStr, out int height))
                {
                    Console.WriteLine($"身長: {height}");
                }
                else
                {
                    Console.WriteLine("身長の値が不正です。");
                }
            }
        }
    }
}
年齢: 30
身長の値が不正です。

XMLの場合も同様に、文字列として取得した値をTryParseで検証します。

例えば、System.XmlXmlDocumentXDocumentで値を取得し、数値変換を行います。

このように、外部データの数値チェックにはTryParseが欠かせません。

変換失敗時の処理を適切に行うことで、データの整合性を保ちつつ安全にアプリケーションを動作させられます。

null 許容型と TryParse

int? と Nullable.TryParse

C#では、int?のようなnull許容型(Nullable型)を使うことで、値が存在しない状態を表現できます。

文字列から数値への変換で、変換に失敗した場合にnullを返したいケースに便利です。

標準のint.TryParseoutパラメータに非nullのintを返すため、変換失敗時は0がセットされます。

しかし、int?型に直接対応するNullable.TryParseは存在しません。

そのため、int?に変換結果を格納したい場合は、TryParseの結果を利用して自分でnullを割り当てる必要があります。

以下の例では、文字列をint?に変換し、変換失敗時はnullを返すメソッドを実装しています。

using System;
class Program
{
    static int? NullableTryParse(string input)
    {
        if (int.TryParse(input, out int result))
        {
            return result;
        }
        else
        {
            return null;
        }
    }
    static void Main()
    {
        string validInput = "123";
        string invalidInput = "abc";
        int? value1 = NullableTryParse(validInput);
        int? value2 = NullableTryParse(invalidInput);
        Console.WriteLine($"入力 '{validInput}' の変換結果: {(value1.HasValue ? value1.Value.ToString() : "null")}");
        Console.WriteLine($"入力 '{invalidInput}' の変換結果: {(value2.HasValue ? value2.Value.ToString() : "null")}");
    }
}
入力 '123' の変換結果: 123
入力 'abc' の変換結果: null

このように、TryParseの結果を利用してint?に変換することで、変換失敗をnullで表現できます。

これにより、値の有無を明確に区別できるため、後続の処理でnullチェックを行うことが可能です。

許容型と非許容型の相互変換

int?(null許容型)とint(非許容型)は相互に変換できますが、注意が必要です。

int?からintに変換する際は、nullの場合に例外が発生するため、必ずHasValueプロパティやGetValueOrDefaultメソッドで安全に扱う必要があります。

以下は、int?からintへの変換例です。

using System;
class Program
{
    static void Main()
    {
        int? nullableValue = 100;
        int nonNullableValue;
        if (nullableValue.HasValue)
        {
            nonNullableValue = nullableValue.Value;
            Console.WriteLine($"nullableValueから変換: {nonNullableValue}");
        }
        else
        {
            Console.WriteLine("nullableValueはnullです。");
        }
        // GetValueOrDefaultを使う場合(nullなら0を返す)
        int defaultValue = nullableValue.GetValueOrDefault();
        Console.WriteLine($"GetValueOrDefaultの結果: {defaultValue}");
    }
}
nullableValueから変換: 100
GetValueOrDefaultの結果: 100

逆に、intからint?への変換は暗黙的に行えます。

int nonNullable = 50;
int? nullable = nonNullable; // 暗黙的変換
Console.WriteLine(nullable); // 50

TryParseで得たint値をint?に代入するのも簡単です。

if (int.TryParse("123", out int result))
{
    int? nullableResult = result; // 暗黙的に変換される
    Console.WriteLine(nullableResult);
}

このように、許容型と非許容型は用途に応じて使い分け、nullの有無を明示的に管理することが重要です。

TryParseと組み合わせることで、より柔軟で安全な数値変換処理が実現できます。

カスタム書式と FormatProvider

IFormatProvider を実装するケース

C#の数値変換において、IFormatProviderは数値の書式や文化依存の情報を提供するインターフェースです。

標準ではCultureInfoNumberFormatInfoIFormatProviderを実装しており、TryParseメソッドに渡すことで、数値の解析時に文化特有の書式を適用できます。

しかし、標準のCultureInfoNumberFormatInfoでは対応できない独自の書式ルールが必要な場合、自分でIFormatProviderを実装することがあります。

例えば、特殊な小数点記号や桁区切り文字を使う独自フォーマットを扱いたい場合です。

以下は、IFormatProviderICustomFormatterを実装して、独自の数値書式を提供する簡単な例です。

この例では、小数点記号を「#」に置き換えたカスタムフォーマッターを作成します。

using System;
using System.Globalization;
public class CustomNumberFormatProvider : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
            return this;
        return null;
    }
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        if (arg == null)
            return string.Empty;
        if (arg is IFormattable formattable)
        {
            // 通常の書式で文字列化
            string result = formattable.ToString(format, CultureInfo.InvariantCulture);
            // 小数点を '#' に置換
            return result.Replace('.', '#');
        }
        return arg.ToString();
    }
}
class Program
{
    static void Main()
    {
        double value = 1234.56;
        var provider = new CustomNumberFormatProvider();
        // ToStringでカスタムフォーマッターを使う例
        string formatted = value.ToString("F2", provider);
        Console.WriteLine($"カスタム書式: {formatted}");
        // TryParseはカスタムIFormatProviderを直接サポートしないため、
        // 解析時は標準のNumberFormatInfoを使う必要があります。
    }
}
カスタム書式: 1234#56

この例では、ToStringでカスタムフォーマッターを使い、小数点を「#」に置き換えています。

ただし、TryParseIFormatProviderを受け取りますが、ICustomFormatterはサポートしていません。

そのため、解析時にカスタムの小数点記号を扱うには、NumberFormatInfoをカスタマイズする方法が一般的です。

ユーザー定義の NumberFormatInfo

NumberFormatInfoは、数値の小数点記号、桁区切り文字、通貨記号などの書式情報を保持するクラスで、CultureInfoの一部として利用されます。

TryParseの第3引数にIFormatProviderとして渡すことで、数値解析時の書式ルールをカスタマイズできます。

独自の数値書式を扱いたい場合は、NumberFormatInfoをコピーして必要なプロパティを変更し、それをTryParseに渡す方法が一般的です。

以下は、小数点記号を「#」、桁区切り文字を「@」に変更したNumberFormatInfoを使ってdouble.TryParseを行う例です。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "1@234#56";
        // InvariantCultureのNumberFormatInfoをコピー
        NumberFormatInfo customFormat = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
        // 小数点記号を '#' に変更
        customFormat.NumberDecimalSeparator = "#";
        // 桁区切り文字を '@' に変更
        customFormat.NumberGroupSeparator = "@";
        bool success = double.TryParse(input, NumberStyles.Number, customFormat, out double result);
        Console.WriteLine($"変換成功: {success}, 結果: {result}");
    }
}
変換成功: True, 結果: 1234.56

このように、NumberFormatInfoのプロパティを変更することで、標準のカルチャにない独自の数値表現を解析できます。

TryParseはこのカスタムフォーマットを尊重して変換を行うため、特殊な書式の入力を安全に処理可能です。

NumberFormatInfoでカスタマイズできる主なプロパティは以下の通りです。

プロパティ名説明
NumberDecimalSeparator小数点記号
NumberGroupSeparator桁区切り文字
CurrencySymbol通貨記号
PositiveSign正の符号(通常は “+”)
NegativeSign負の符号(通常は “-“)
NumberGroupSizes桁区切りのグループサイズ配列

これらを適切に設定することで、さまざまな文化や独自仕様に対応した数値解析が可能になります。

拡張メソッドによるコード整理

汎用の TryParseWrapper

TryParseメソッドは便利ですが、毎回outパラメータを使って結果を受け取り、成功・失敗の判定を行うコードを書くのは冗長になりがちです。

そこで、拡張メソッドを使ってTryParseの処理をラップし、よりシンプルに扱えるようにする方法があります。

以下は、任意の数値型に対応した汎用的なTryParseWrapper拡張メソッドの例です。

このメソッドは、変換に成功した場合は変換後の値を返し、失敗した場合はnullを返します。

戻り値の型はNullable<T>T?となるため、呼び出し側でnullチェックが可能です。

using System;
public static class TryParseExtensions
{
    public static T? TryParseOrNull<T>(this string input, TryParseHandler<T> tryParse)
        where T : struct
    {
        if (tryParse(input, out T result))
        {
            return result;
        }
        else
        {
            return null;
        }
    }
    public delegate bool TryParseHandler<T>(string s, out T result);
}
class Program
{
    static void Main()
    {
        string validInt = "123";
        string invalidInt = "abc";
        int? parsedValid = validInt.TryParseOrNull(int.TryParse);
        int? parsedInvalid = invalidInt.TryParseOrNull(int.TryParse);
        Console.WriteLine($"有効な整数の変換結果: {(parsedValid.HasValue ? parsedValid.Value.ToString() : "null")}");
        Console.WriteLine($"無効な整数の変換結果: {(parsedInvalid.HasValue ? parsedInvalid.Value.ToString() : "null")}");
    }
}
有効な整数の変換結果: 123
無効な整数の変換結果: null

この拡張メソッドは、int.TryParseだけでなく、double.TryParsedecimal.TryParseなど、同じシグネチャのTryParseメソッドなら何でも利用可能です。

呼び出し側はTryParseの詳細を意識せずに、簡潔に変換処理を記述できます。

Fluent API 風の実装

さらにコードの可読性や使いやすさを高めるために、Fluent API風の拡張メソッドを作成することもできます。

Fluent APIとは、メソッドチェーンで処理を連結し、直感的に操作できる設計スタイルです。

以下は、文字列から数値への変換をFluentに行い、成功時と失敗時の処理をチェーンで指定できるサンプル実装です。

using System;
public class ParseResult<T> where T : struct
{
    public bool Success { get; }
    public T Value { get; }
    public ParseResult(bool success, T value)
    {
        Success = success;
        Value = value;
    }
    public ParseResult<T> OnSuccess(Action<T> action)
    {
        if (Success)
        {
            action(Value);
        }
        return this;
    }
    public ParseResult<T> OnFailure(Action action)
    {
        if (!Success)
        {
            action();
        }
        return this;
    }
}
public static class FluentTryParseExtensions
{
    public static ParseResult<T> TryParseFluent<T>(this string input, TryParseHandler<T> tryParse)
        where T : struct
    {
        bool success = tryParse(input, out T result);
        return new ParseResult<T>(success, result);
    }
    public delegate bool TryParseHandler<T>(string s, out T result);
}
class Program
{
    static void Main()
    {
        string input = "456";
        input.TryParseFluent(int.TryParse)
             .OnSuccess(value => Console.WriteLine($"変換成功: {value}"))
             .OnFailure(() => Console.WriteLine("変換失敗"));
        string invalidInput = "xyz";
        invalidInput.TryParseFluent(int.TryParse)
                    .OnSuccess(value => Console.WriteLine($"変換成功: {value}"))
                    .OnFailure(() => Console.WriteLine("変換失敗"));
    }
}
変換成功: 456
変換失敗

このFluent API風の実装では、TryParseFluentメソッドが変換結果をラップしたParseResult<T>オブジェクトを返します。

OnSuccessOnFailureメソッドをチェーンで呼び出すことで、成功時と失敗時の処理を分かりやすく記述できます。

この設計により、条件分岐のネストを減らし、コードの可読性と保守性を向上させられます。

特に複数の変換や検証を連続して行う場合に効果的です。

パフォーマンスの観点

大量変換時のメモリと速度

大量の文字列を数値に変換する処理では、パフォーマンスが重要な課題となります。

特に、ループ内で何千回、何万回とTryParseを呼び出す場合、メモリ使用量や処理速度に大きな影響を与えます。

TryParse自体は例外を発生させないため高速ですが、文字列の生成やガベージコレクションの負荷がボトルネックになることがあります。

例えば、文字列の分割やトリム処理を頻繁に行うと、不要なメモリ割り当てが増え、パフォーマンス低下の原因になります。

以下のポイントに注意すると効率的な大量変換が可能です。

  • 文字列の再利用

可能な限り新しい文字列を生成せず、既存の文字列を使い回すことでメモリ割り当てを減らせます。

  • 不要な文字列操作の削減

TrimSubstringなどの文字列操作は新しい文字列を生成するため、必要最低限に抑えます。

  • バッチ処理の活用

一度に大量のデータを処理する場合は、バッチ単位で処理し、メモリ使用量をコントロールします。

  • 並列処理の検討

CPUコアを活用して並列に変換処理を行うことで、全体の処理時間を短縮できます。

ただし、スレッド間の競合やメモリ使用量に注意が必要です。

以下は、大量の文字列をintに変換する簡単なベンチマーク例です。

using System;
using System.Diagnostics;
class Program
{
    static void Main()
    {
        const int count = 1_000_000;
        string[] inputs = new string[count];
        for (int i = 0; i < count; i++)
        {
            inputs[i] = (i % 10000).ToString();
        }
        int sum = 0;
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < count; i++)
        {
            if (int.TryParse(inputs[i], out int value))
            {
                sum += value;
            }
        }
        sw.Stop();
        Console.WriteLine($"合計: {sum}");
        Console.WriteLine($"処理時間: {sw.ElapsedMilliseconds} ms");
    }
}

このような処理では、文字列の生成を最小限にし、TryParseの呼び出しを効率化することが重要です。

Span<char> を使った高速化

.NET Core 以降では、Span<T>構造体を使うことで、文字列の部分的な切り出しや変換をメモリ割り当てなしに行えます。

Span<char>は文字列の一部を参照する軽量な型で、コピーを伴わないため高速です。

TryParseにはReadOnlySpan<char>を受け取るオーバーロードが用意されており、これを活用すると文字列の部分解析や大量データの高速処理が可能になります。

以下は、Span<char>を使って文字列の一部をintに変換する例です。

using System;
class Program
{
    static void Main()
    {
        string input = "12345abc6789";
        ReadOnlySpan<char> span = input.AsSpan(0, 5); // "12345"
        bool success = int.TryParse(span, out int result);
        Console.WriteLine($"変換成功: {success}, 結果: {result}");
    }
}
変換成功: True, 結果: 12345

この例では、文字列の先頭5文字だけをSpan<char>で切り出し、TryParseに渡しています。

Span<char>は新しい文字列を生成しないため、メモリ割り当てが発生せず高速です。

大量のデータを扱う場合、Span<char>を使うことで以下のメリットがあります。

  • メモリ割り当ての削減

部分文字列を作成せずに参照だけを渡せるため、GC負荷が軽減されます。

  • 高速な部分解析

文字列の一部だけを効率的に解析できるため、パフォーマンスが向上します。

  • 安全なメモリ操作

Span<T>は安全にメモリを扱うため、バッファオーバーランなどのリスクが低減します。

ただし、Span<T>はスタック上に存在するため、非同期メソッドやクロージャ内での使用には制限があります。

これらの制約を理解した上で活用してください。

まとめると、大量の数値変換処理では、TryParseの使い方に加え、文字列操作の最適化やSpan<char>の活用がパフォーマンス向上に効果的です。

適切な設計で高速かつメモリ効率の良い処理を実現しましょう。

まとめ

この記事では、C#のTryParseを使った文字列から数値への安全な変換方法と失敗時のハンドリング、文化依存の書式対応、拡張メソッドによるコード整理、パフォーマンス最適化まで幅広く解説しました。

TryParseの基本的な使い方から、CultureInfoNumberStylesを活用した柔軟な変換、Span<char>による高速化まで理解でき、実践的な数値変換処理の設計に役立ちます。

関連記事

Back to top button