خداحافظی با Overloadهای بیپایان: قدرت Generic Math با INumber در داتنت مدرن
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۶/۲۲ ۱۵:۱۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
int, long, double و decimal به صورت جداگانه پیادهسازی (Overload) کنید؟ این یک کار تکراری و خستهکنندهاست که خوانایی کد را کاهش داده و احتمال خطا را بالا میبرد. خوشبختانه، از داتنت ۷ به بعد، با معرفی Generic Math و اینترفیس INumber، این دوران به پایان رسیده است. این مطلب به شما نشان میدهد که چگونه با استفاده از این قابلیت مدرن، کدهای عددی خود را یک بار برای همیشه بنویسید: تمیزتر، انعطافپذیرتر و آیندهنگرانهتر.INumber1024 تقسیم کند. این عملیات در محاسبات مربوط به حجم دادهها (مانند تبدیل بایت به کیلوبایت، مگابایت و...) بسیار رایج است. در گذشته، یک پیادهسازی ساده برای نوع long به شکل زیر بود:// روش سنتی و محدود به نوع long
public static long DivideBy1024(this long number, byte times = 1)
{
long divisor = (long)Math.Pow(1024, times);
return number / divisor;
}long قابل استفاده است. اگر بخواهیم همین کار را برای int، double یا decimal انجام دهیم، باید متدهای مشابهی را برای هر کدام از این انواع بازنویسی کنیم. این کار نه تنها حجم کد را افزایش میدهد، بلکه نگهداری آن را نیز دشوار میسازد.INumberINumber که در فضای نام System.Numerics قرار دارد، یک تغییر بزرگ در برنامهنویسی داتنت ایجاد کرده است. این اینترفیس یک قرارداد مشترک برای تمام انواع عددی اصلی فراهم میکند و به ما اجازه میدهد تا متدهایی بنویسیم که به صورت ژنریک (Generic) روی هر نوع عددی کار میکنند. INumber شامل تعاریف اپراتورهای ریاضی پایه (+, -, *, /)، مقادیر ثابت (مانند One و Zero) و متدهای استاتیک برای ساختن مقادیر از انواع دیگر است. حالا بیایید متد DivideBy1024 را با استفاده از این قابلیت بازنویسی کنیم:using System.Numerics;
public static class NumericExtensions
{
public static T DivideBy1024<T>(this T number, byte times = 1) where T : INumber<T>
{
// 1. ساختن مقدار ثابت 1024 از نوع ژنریک T
T divisorBase = T.CreateChecked(1024);
// 2. پیادهسازی ژنریک برای عمل توان (جایگزین Math.Pow)
T divisor = Power(divisorBase, times);
// 3. استفاده از اپراتور تقسیم که برای همه انواع INumber تعریف شده
return number / divisor;
}
/// <summary>
/// یک پیادهسازی ژنریک و ساده از عمل توان برای انواع عددی.
/// </summary>
private static T Power<T>(T baseValue, byte exponent) where T : INumber<T>
{
if (exponent == 0)
return T.One; // استفاده از مقدار ثابت T.One
T result = baseValue;
for (int i = 1; i < exponent; i++)
{
result *= baseValue; // استفاده از اپراتور *=
}
return result;
}
}where T : INumber: این مهمترین بخش کد است. ما به کامپایلر میگوییم که نوع T باید حتماً اینترفیس INumber را پیادهسازی کرده باشد. این قید به ما اطمینان میدهد که تمام اپراتورها و متدهای مورد نیاز در دسترس هستند.T.CreateChecked(1024): از آنجایی که نمیتوانیم عدد 1024 را مستقیماً به نوع نامشخص T تبدیل کنیم، از متد استاتیک CreateChecked استفاده میکنیم. این متد به صورت امن، یک مقدار را به نوع T تبدیل میکند.Power: Math.Pow برای انواع ژنریک طراحی نشده است. بنابراین، یک متد کمکی ساده نوشتیم که با استفاده از اپراتور *= و مقدار T.One (که هر دو بخشی از INumber هستند)، عمل توان را برای هر نوع عددی شبیهسازی میکند.DivideBy1024 را روی انواع مختلفی فراخوانی کنید، بدون آنکه نگران نوع داده باشید:long memoryInBytes = 4_294_967_296; // 4 گیگابایت
long memoryInKilobytes = memoryInBytes.DivideBy1024(); // نتیجه: 4194304
long memoryInMegabytes = memoryInBytes.DivideBy1024(times: 2); // نتیجه: 4096
long memoryInGigabytes = memoryInBytes.DivideBy1024(3); // نتیجه: 4
Console.WriteLine($"Memory in GB: {memoryInGigabytes}");
// --- استفاده برای انواع دیگر ---
double preciseValue = 1536.5;
double resultDouble = preciseValue.DivideBy1024(); // کار میکند!
Console.WriteLine($"Double Result: {resultDouble}"); // نتیجه: 1.50048828125
decimal financialValue = 204800.00m;
decimal resultDecimal = financialValue.DivideBy1024(); // کار میکند!
Console.WriteLine($"Decimal Result: {resultDecimal}"); // نتیجه: 200.00long, double و decimal بدون هیچ تغییری کار کند. این یعنی کد کمتر، نگهداری آسانتر و خوانایی بالاتر.INumber دیگر یک انتخاب نیست، بلکه یک روش استاندارد و بهینه برای کار با دادههای عددی در داتنت است. با پذیرش این الگو، شما میتوانید: