【C#】TryParseで文字列を安全に数値変換する方法と失敗時のハンドリング
文字列を数値に安全に変換したいなら、int.TryParse
などのTryParse系が最適です。
bool値で成功可否を受け取り、成功時はout引数に値が入り、失敗しても例外が出ず処理を続行できます。
intだけでなくdoubleやdecimalなど多様な型で同じ構文を使えます。
TryParse の基本
C#で文字列を数値に変換する際、TryParse
メソッドは非常に便利で安全な方法です。
ここでは、TryParse
の基本的な特徴や、従来のParse
メソッドとの違いについて詳しく解説します。
TryParse と Parse の違い
Parse
メソッドは、文字列を数値型に変換する際に使われる代表的なメソッドです。
例えば、int.Parse
は文字列を整数に変換しますが、変換に失敗すると例外FormatException
やOverflowException
が発生します。
一方、TryParse
は変換が成功したかどうかをbool
型で返し、失敗しても例外を発生させません。
これにより、例外処理のコストを抑えつつ安全に変換を試みることができます。
以下のサンプルコードは、Parse
とTryParse
の違いを示しています。
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
が整数に変換可能な文字列なので、success
はtrue
となり、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
このように、TryParse
はNumberStyles
やIFormatProvider
を指定することで、カルチャ依存の数値表現にも対応できます。
無限大・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
に変換し、精度を保ったまま扱えます。
以下の例で、decimal
とdouble
の違いを比較します。
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 などその他の型
TryParse
はint
やdouble
、decimal
以外にも多くの数値型で利用可能です。
代表的なものを以下に示します。
型名 | サイズ(ビット) | 範囲 | 用途例 |
---|---|---|---|
long | 64 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 大きな整数の扱い |
short | 16 | -32,768 ~ 32,767 | 小さな整数の扱い |
byte | 8 | 0 ~ 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.TryParse
やdecimal.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進数変換時は符号や小数点は扱えません。
これらのCultureInfo
とNumberStyles
の組み合わせを活用することで、文化依存の数値表現や特殊な書式を安全かつ柔軟に処理できます。
入力の多様性に対応する際は、これらのオプションを適切に設定してください。
失敗時のハンドリング戦略
既定値を返す分岐
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.Xml
のXmlDocument
やXDocument
で値を取得し、数値変換を行います。
このように、外部データの数値チェックにはTryParse
が欠かせません。
変換失敗時の処理を適切に行うことで、データの整合性を保ちつつ安全にアプリケーションを動作させられます。
null 許容型と TryParse
int? と Nullable.TryParse
C#では、int?
のようなnull許容型(Nullable型)を使うことで、値が存在しない状態を表現できます。
文字列から数値への変換で、変換に失敗した場合にnull
を返したいケースに便利です。
標準のint.TryParse
はout
パラメータに非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
は数値の書式や文化依存の情報を提供するインターフェースです。
標準ではCultureInfo
やNumberFormatInfo
がIFormatProvider
を実装しており、TryParse
メソッドに渡すことで、数値の解析時に文化特有の書式を適用できます。
しかし、標準のCultureInfo
やNumberFormatInfo
では対応できない独自の書式ルールが必要な場合、自分でIFormatProvider
を実装することがあります。
例えば、特殊な小数点記号や桁区切り文字を使う独自フォーマットを扱いたい場合です。
以下は、IFormatProvider
とICustomFormatter
を実装して、独自の数値書式を提供する簡単な例です。
この例では、小数点記号を「#」に置き換えたカスタムフォーマッターを作成します。
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
でカスタムフォーマッターを使い、小数点を「#」に置き換えています。
ただし、TryParse
はIFormatProvider
を受け取りますが、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.TryParse
やdecimal.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>
オブジェクトを返します。
OnSuccess
とOnFailure
メソッドをチェーンで呼び出すことで、成功時と失敗時の処理を分かりやすく記述できます。
この設計により、条件分岐のネストを減らし、コードの可読性と保守性を向上させられます。
特に複数の変換や検証を連続して行う場合に効果的です。
パフォーマンスの観点
大量変換時のメモリと速度
大量の文字列を数値に変換する処理では、パフォーマンスが重要な課題となります。
特に、ループ内で何千回、何万回とTryParse
を呼び出す場合、メモリ使用量や処理速度に大きな影響を与えます。
TryParse
自体は例外を発生させないため高速ですが、文字列の生成やガベージコレクションの負荷がボトルネックになることがあります。
例えば、文字列の分割やトリム処理を頻繁に行うと、不要なメモリ割り当てが増え、パフォーマンス低下の原因になります。
以下のポイントに注意すると効率的な大量変換が可能です。
- 文字列の再利用
可能な限り新しい文字列を生成せず、既存の文字列を使い回すことでメモリ割り当てを減らせます。
- 不要な文字列操作の削減
Trim
やSubstring
などの文字列操作は新しい文字列を生成するため、必要最低限に抑えます。
- バッチ処理の活用
一度に大量のデータを処理する場合は、バッチ単位で処理し、メモリ使用量をコントロールします。
- 並列処理の検討
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
の基本的な使い方から、CultureInfo
やNumberStyles
を活用した柔軟な変換、Span<char>
による高速化まで理解でき、実践的な数値変換処理の設計に役立ちます。