double.TryParse() برای تبدیل رشته به عدد اعشاری دودقت به کار میرود و در سناریوهایی مانند مختصات جغرافیایی، محاسبات علمی و آمار مانع تبدیل خطای قابل انتظار به استثنای کنترلی میشود. طراحی درست فقط فراخوانی متد نیست؛ باید قرارداد ورودی، مرز نوع، فرهنگ، خطای کاربر و اعتبار دامنه نیز مشخص باشد.
دامنه یا قرارداد اصلی این نوع تقریباً ±۱٫۷۹۷۶۹۳۱۳۴۸۶۲۳۱۵۷×۱۰^۳۰۸ با حدود ۱۵ تا ۱۷ رقم دقت است. double برای علم و مهندسی مناسب است، اما برابری مستقیم مقادیر اعشاری باید با تلورانس انجام شود. به همین دلیل نتیجه true صرفاً موفقیت تبدیل نحوی را نشان میدهد و مجاز بودن مقدار برای عملیات جاری باید در مرحله بعد بررسی شود.
مثالهای عملی گامبهگام
ده مثال زیر از تبدیل پایه آغاز میشوند و به سناریوهای مجموعهداده، null، مرز، خطای Culture و کارایی میرسند. هر مثال برای double.TryParse() طراحی شده و نتیجه نمونه را بدون نیاز به اجرای اولیه نشان میدهد.
مثال شماره 1: تبدیل مقدار ثابت برای مختصات
در سادهترین حالت، رشتهای که از نظر قالب و دامنه با نوع double سازگار است به متد داده میشود. بررسی مقدار بازگشتی قبل از استفاده از result باعث میشود مسیر موفق و ناموفق صریح باقی بماند.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string input = "51.3890";
bool success = double.TryParse(input, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value);
Console.WriteLine($"success={success}; value={value.ToString(CultureInfo.InvariantCulture)}");
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| موفقیت | True |
| مقدار | 51.3890 |
این الگو برای دریافت مختصات از فرم یا فایل تنظیمات مناسب است. مقدار result فقط در شاخه موفقیت وارد منطق اصلی میشود و مقدار پیشفرض هرگز با داده واقعی اشتباه گرفته نمیشود.
مثال شماره 2: پردازش دستهای دادههای ورودی
در ورود اطلاعات واقعی معمولاً با مجموعهای از رشتهها روبهرو هستیم. این مثال رکوردهای مناسب مختصات جغرافیایی، محاسبات علمی و آمار را تفکیک میکند و بهجای متوقف کردن کل عملیات، تعداد خطاها را برای گزارش کیفیت داده نگه میدارد.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string[] rows = { "51.3890", "35.6892", "12.3.4", "0.000125" };
int accepted = 0;
int rejected = 0;
foreach (string text in rows)
{
if (double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value))
{
accepted++;
Console.WriteLine($"accepted: {value.ToString(CultureInfo.InvariantCulture)}");
}
else
{
rejected++;
Console.WriteLine($"rejected: {text}");
}
}
Console.WriteLine($"accepted={accepted}; rejected={rejected}");
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| رکوردهای پذیرفته | 3 |
| رکوردهای ردشده | 1 |
تفکیک رکورد معتبر و نامعتبر، پایه یک فرایند Import قابل پایش است. در پروژه سازمانی بهتر است متن ورودی، شماره ردیف و دلیل رد نیز در گزارش خطا ثبت شود، اما داده حساس نباید بدون پالایش وارد لاگ شود.
مثال شماره 3: ساخت Projection شبیه SELECT با LINQ
گاهی هدف، تبدیل هر ردیف به یک مقدار Nullable است تا ساختار و ترتیب داده حفظ شود. استفاده از مقدار null برای شکست تبدیل، با صفر واقعی تفاوت دارد و اجازه میدهد لایه گزارشگیری وضعیت داده ناقص را درست نمایش دهد.
using System;
using Globalization;
using Linq;
internal static class Program
{
private static void Main()
{
string[] raw = { "51.3890", "12.3.4", "35.6892" };
double?[] converted = raw
.Select(text => double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value)
? value
: (double?)null)
.ToArray();
string report = string.Join(
" | ",
converted.Select(value => value.HasValue
? value.Value.ToString(CultureInfo.InvariantCulture)
: "نامعتبر")
);
Console.WriteLine(report);
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| ترتیب خروجی | 51.3890 | نامعتبر | 35.6892 |
این الگو در ViewModel، گزارش و تبدیل DTO کاربرد دارد. اگر null از نظر دامنه کسبوکار معنی مستقلی دارد، بهتر است یک Result صریح شامل مقدار، وضعیت و پیام خطا تعریف شود تا ابهام باقی نماند.
مثال شماره 4: فیلتر داده معتبر شبیه شرط WHERE
صحیح بودن قالب تنها شرط پذیرش نیست. در این نمونه ابتدا double.TryParse() اجرا میشود و سپس قانون کسبوکار غیرمنفی بودن برای مختصات اعمال میگردد؛ بنابراین اعتبار نحوی و معنایی از هم جدا هستند.
using System;
using Globalization;
using Linq;
internal static class Program
{
private static void Main()
{
string[] raw = { "-1", "0.000125", "35.6892", "12.3.4" };
string[] accepted = raw
.Where(text =>
double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value) &&
Convert.ToDecimal(value, CultureInfo.InvariantCulture) >= 0)
.ToArray();
Console.WriteLine(string.Join(", ", accepted));
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| ورودیهای پذیرفته | 0.000125, 35.6892 |
قرار دادن تبدیل قبل از مقایسه از استفاده ناخواسته مقدار پیشفرض جلوگیری میکند. برای قواعد پیچیدهتر، شرط دامنه را به متدی نامدار انتقال دهید تا آزمون واحد و پیام خطای دقیقتری داشته باشید.
مثال شماره 5: ترکیب تبدیل با محاسبه
پس از تبدیل موفق میتوان مقدار مختصات را وارد محاسبه کرد. شاخهبندی واضح تضمین میکند محاسبه فقط با داده معتبر انجام شود و نتیجه نامعتبر بهصورت تصادفی وارد صورتحساب، گزارش یا تصمیم سامانه نشود.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string input = "0.000125";
if (double.TryParse(input, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value))
{
decimal normalized = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
decimal adjusted = normalized * 1.10m;
Console.WriteLine(adjusted.ToString("0.####", CultureInfo.InvariantCulture));
}
else
{
Console.WriteLine("ورودی نامعتبر");
}
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| ورودی | 0.000125 |
| نتیجه پس از ضریب ۱٫۱۰ | مقدار محاسبهشده معتبر |
نوع محاسبات بعدی باید با هدف سازگار باشد. double برای علم و مهندسی مناسب است، اما برابری مستقیم مقادیر اعشاری باید با تلورانس انجام شود. تبدیل اولیه موفق، خطاهای گرد کردن یا انتخاب نوع نامناسب در مراحل بعدی را برطرف نمیکند.
مثال شماره 6: رفتار با null
ورودی null در مسیرهای فرم، JSON، پایگاه داده و تنظیمات رایج است. متد TryParse برای null استثنای قالب تولید نمیکند و false میدهد؛ بااینحال کد نباید مقدار پیشفرض result را بهعنوان داده معتبر ذخیره کند.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string? input = null;
bool success = double.TryParse(input, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value);
Console.WriteLine($"success={success}");
Console.WriteLine($"default-value={value.ToString(CultureInfo.InvariantCulture)}");
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| موفقیت | False |
| result | مقدار پیشفرض نوع |
تصمیم نهایی درباره null وابسته به قرارداد فیلد است: ممکن است فیلد اختیاری باشد، یا نبودنش خطای اعتبارسنجی محسوب شود. این تصمیم را بیرون از TryParse و با پیام مناسب به کاربر پیاده کنید.
مثال شماره 7: کنترل مرز نوع و سرریز
محدوده double برابر تقریباً ±۱٫۷۹۷۶۹۳۱۳۴۸۶۲۳۱۵۷×۱۰^۳۰۸ با حدود ۱۵ تا ۱۷ رقم دقت است. آزمون مقادیر مرزی نشان میدهد TryParse علاوه بر قالب، قابلنمایش بودن مقدار در نوع مقصد را کنترل میکند و در سرریز قابل تشخیص false برمیگرداند.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string[] boundaries = { "-1.7976931348623157E+308", "1.7976931348623157E+308", "invalid" };
foreach (string text in boundaries)
{
bool success = double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value);
string parsed = success
? value.ToString(CultureInfo.InvariantCulture)
: "رد شد";
Console.WriteLine($"{text} => {parsed}");
}
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| کمینه | بررسی شد |
| بیشینه | بررسی شد |
| خارج از قرارداد | رد یا طبق قواعد نوع مدیریت شد |
double برای علم و مهندسی مناسب است، اما برابری مستقیم مقادیر اعشاری باید با تلورانس انجام شود. برای float و double برخی رشتههای بسیار بزرگ در نسخههای جدید .NET ممکن است به Infinity گرد شوند؛ اگر بینهایت برای دامنه شما مجاز نیست، IsFinite را نیز کنترل کنید.
مثال شماره 8: سناریوی گزارشگیری سازمانی
گزارشهای مختصات جغرافیایی، محاسبات علمی و آمار باید در برابر یک ردیف خراب مقاوم باشند. نمونه حاضر فقط رکوردهای معتبر را وارد مجموع میکند و تعداد آنها را جداگانه گزارش میدهد تا تصمیمگیرنده از کیفیت ورودی آگاه باشد.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string[] source = { "51.3890", "35.6892", "bad", "0.000125" };
decimal total = 0m;
int validCount = 0;
foreach (string text in source)
{
if (double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value))
{
total += Convert.ToDecimal(value, CultureInfo.InvariantCulture);
validCount++;
}
}
Console.WriteLine($"valid={validCount}; total={total.ToString(CultureInfo.InvariantCulture)}");
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| تعداد معتبر | 3 |
| تعداد حذفشده | 1 |
| مجموع | از سه مقدار معتبر |
حذف خاموش رکورد نامعتبر میتواند گزارش را گمراه کند. بهتر است کنار عدد نهایی، تعداد رکورد ردشده و پیوند به گزارش خطا نمایش داده شود و برای آستانه خطا هشدار عملیاتی تعریف گردد.
مثال شماره 9: روش اشتباه و نسخه اصلاحشده برای Culture
یکی از رایجترین خطاها تکیه بر Culture جاری ماشین است. این مثال نتیجه روش مبهم را با نسخهای مقایسه میکند که هم سبک عدد و هم فرهنگ را صریح اعلام کرده است.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
string input = "1.234,56";
bool wrong = double.TryParse(
input,
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture,
out double wrongValue
);
CultureInfo german = CultureInfo.GetCultureInfo("de-DE");
bool correct = double.TryParse(
input,
NumberStyles.Float | NumberStyles.AllowThousands,
german,
out double correctValue
);
Console.WriteLine($"invariant={wrong}; de-DE={correct}");
Console.WriteLine(correctValue.ToString(CultureInfo.InvariantCulture));
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| ورودی | 1.234,56 |
| نتیجه صحیح | 1234.56 |
در رشته آلمانی نقطه جداکننده هزارگان و ویرگول جداکننده اعشار است. تعیین صریح CultureInfo از وابستگی نتیجه به زبان سیستمعامل یا سرور جلوگیری میکند.
مثال شماره 10: استفاده از ReadOnlySpan برای مسیر پرتکرار
در Parserهای فایل، پروتکل و خطوط بزرگ میتوان بخشی از متن را بدون ساخت substring جداگانه به overload مبتنی بر ReadOnlySpan داد. این روش در حجم بالا تخصیص حافظه را کاهش میدهد، هرچند اندازهگیری واقعی با Benchmark ضروری است.
using System;
using Globalization;
internal static class Program
{
private static void Main()
{
ReadOnlySpan<char> token = "51.3890".AsSpan();
bool success = double.TryParse(
token,
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture,
out double value
);
Console.WriteLine(success
? value.ToString(CultureInfo.InvariantCulture)
: "نامعتبر");
}
}
| شاخص خروجی | نتیجه نمونه |
|---|
| تخصیص substring | نیاز نیست |
| نتیجه | 51.3890 |
بهینهسازی Span زمانی ارزشمند است که پروفایلر فشار تخصیص را نشان دهد. برای فرم معمولی، خوانایی و پیام اعتبارسنجی مهمتر است؛ اما در پردازش میلیونها توکن، کاهش تخصیص میتواند اثر قابل اندازهگیری داشته باشد.
سؤالات متداول
پرسش 1: double.TryParse() دقیقاً چه مسئلهای را حل میکند؟
double.TryParse() متن را با قرارداد نوع مقصد بررسی میکند و بدون تبدیل شکست قابل انتظار به استثنا، وضعیت موفقیت را برمیگرداند. این ویژگی در ورودی کاربر، فایل، API و تنظیمات ارزشمند است. حوزه رایج این مقاله مختصات جغرافیایی، محاسبات علمی و آمار است، اما اعتبار نحوی فقط نخستین مرحله است و قواعد دامنه باید جدا اعمال شوند.
پرسش 2: در صورت شکست double.TryParse() چه اتفاقی برای result میافتد؟
متد false برمیگرداند و result مقدار پیشفرض نوع را خواهد داشت یا نباید معتبر فرض شود. الگوی درست این است که result فقط در شاخه true مصرف شود. تکیه بر صفر، false، تاریخ کمینه یا Guid.Empty میتواند داده نامعتبر را با یک مقدار واقعی اشتباه بگیرد و خطای خاموش ایجاد کند.
پرسش 3: Culture چه اثری بر double.TryParse() دارد؟
اثر Culture به نوع بستگی دارد. اعداد اعشاری، جداکننده هزارگان و تاریخ به فرهنگ حساساند، درحالیکه Guid و bool قرارداد محدودتری دارند. در مرزهای ماشینی CultureInfo.InvariantCulture و قالب مستند، و در رابط کاربر فرهنگ انتخابشده کاربر مناسب است. فرهنگ سرور نباید بهطور تصادفی نتیجه را تعیین کند.
پرسش 4: چگونه double.TryParse() را در نرمافزار تجاری استفاده کنیم؟
یک لایه اعتبارسنجی بسازید که متن خام، نتیجه تبدیل، کد خطا و پیام قابل فهم را برگرداند. سپس قانون کسبوکار مربوط به مختصات جغرافیایی، محاسبات علمی و آمار را اعمال کنید. این جداسازی هم تستپذیری را بالا میبرد و هم امکان گزارش کیفیت داده، مشاوره فنی و توسعه سفارشی جریان Import را فراهم میکند.
پرسش 5: آیا جایگزینی بهتر از double.TryParse() وجود دارد؟
پاسخ به قرارداد ورودی وابسته است. برای قالب ثابت TryParseExact، برای دادهای که خرابی آن باگ است Parse، و برای تبدیلهای سفارشی یک Value Object یا Parser دامنهای مناسبتر است. هدف انتخاب کوتاهترین کد نیست؛ باید خطا، Culture، سازگاری نسخه و پیام کاربر بهروشنی مدیریت شود.
پرسش 6: برای پیادهسازی سازمانی double.TryParse() چه خدماتی لازم میشود؟
در پروژه بزرگ معمولاً طراحی قرارداد داده، اعتبارسنجی چندلایه، تست مرزی، ثبت خطای امن، داشبورد کیفیت ورودی و پایش لازم است. تیم توسعه یا مشاور میتواند این اجزا را متناسب با معماری C# و .NET، حجم داده و الزامات امنیتی پیاده کند. خود متد کوچک است، اما فرایند قابل اعتماد پیرامون آن اهمیت اصلی را دارد.
پرسش 7: خطای رایج هنگام کار با double.TryParse() چیست؟
رایجترین خطا استفاده از result بدون بررسی مقدار بازگشتی است. خطاهای دیگر شامل اتکا به Culture جاری، نادیده گرفتن مرز نوع، یکی گرفتن اعتبار نحوی و معنایی و حذف خاموش رکورد نامعتبر است. نکته اختصاصی این موضوع نیز چنین است: double برای علم و مهندسی مناسب است، اما برابری مستقیم مقادیر اعشاری باید با تلورانس انجام شود.
پرسش 8: کارایی double.TryParse() در پردازش حجیم چگونه است؟
در شکستهای قابل انتظار، TryParse معمولاً از کنترل جریان با exception مناسبتر است. overloadهای ReadOnlySpan میتوانند تخصیص substring را کاهش دهند. بااینحال نتیجه به نسخه .NET، الگوی داده و مسیر اجرا وابسته است؛ پروفایلر و BenchmarkDotNet باید تصمیم را تأیید کنند و بهینهسازی زودهنگام نباید خوانایی را قربانی کند.
پرسش 9: بهترین روش طراحی با double.TryParse() چیست؟
قرارداد ورودی را مستند کنید، Culture و Style را در صورت نیاز صریح بدهید، result را فقط پس از true مصرف کنید، اعتبار دامنه را جدا بسنجید و خطا را با پیام قابل اقدام گزارش دهید. برای مختصات جغرافیایی، محاسبات علمی و آمار آزمونهای مرزی، null، مقدار نامعتبر، ورودی بسیار بزرگ و سناریوی واقعی کسبوکار را در مجموعه تست قرار دهید.
پرسش 10: double.TryParse() با کدام نسخههای C# و .NET سازگار است؟
overload پایه string در نسخههای قدیمی .NET نیز در دسترس است، اما overloadهای Span، UTF-8 و رابطهای Generic Math در نسخههای جدیدتر افزوده شدهاند. پروژه باید مستندات Target Framework خود را مبنا قرار دهد. اگر کتابخانه چندهدفه است، API سطح مشترک را انتخاب یا برای نسخههای جدید مسیر بهینه جدا تعریف کنید.