علیرضا علی‌مردان
علیرضا علی‌مردان
خواندن ۳ دقیقه·۲ سال پیش

عملگرها ریاضی Generic در دات‌نت .NET

عملگرها ریاضی Generic در دات‌نت ابتدا در دات‌نت نسخه 6 معرفی شدند و در ادامه در .NET 7 بسط و گسترش یافته‌اند.

در واقع Generic Math با ترکیب توانایی Generic ها و خصیصه جدیدی بنام static virtuals in interfaces ایجاد شده اند تا به برنامه‌نویسان امکان استفاده از مزایای APIهای static، شامل عملگرها در کدهای عمومی (Generic) را بدهند. بدین شکل که با یک الگوسازی مجموعه‌ای از عملگرها با منطق یکسان را برای طیف گسترده‌ای از داده‌های سازگار فراهم آورند.

خصیصه static virtual members in interfaces از دات‌نت 7 و سی‌شارپ 11 به بعد معرفی شده است.

به این ترتیب زمانیکه شما یک عملگر با منطق و رویه کاری مشابه داشته باشید می‌توانید به این صورت اقدام به تعریف الگویی براساس Generic نمایید و نیاز به پیاده‌سازی جداگانه برای انواع داده مختلف نیست.

static T Add<T>(T left, T right) where T : INumber<T> => left + right;


هم اکنون کتابخانه‌های دات‌نت به عنوان بخشی از LINQ اقدام به ساده سازی توابع Enumerable.Max و Enumerable.Min نموده‌اند.(https://github.com/dotnet/runtime/pull/68183)

خلاصه کلام اینکه با پشتیبانی یک API از یک Interface مثل <INumber<T هر نوعی که این واسط را پیاده سازی کند شامل پشتیبانی آن API خواهد بود.

یک مثال می‌تواند در مورد محاسبه Deviation (انحراف معیار) که برمبنای استفاده از دو تابع Sum و Average کار میکند باشد.(اساساً برای تعیین میزان پراکندگی مجموعه ای از مقادیر استفاده می شود.)

تابع اول Sum است، که مجموعه‌ای از اعداد را با هم جمع می‌کند. این تابع ورودی از نوع <IEnumerable<T دارد که در آن T باید واسط (interface) <Inumber<T را پیاده‌سازی کرده باشد.

این متد خروجی از نوع TResult خواهد داشت، که نوع خروجی نیز به نوبه خود اقدام به پیاده‌سازی <INumber<TResult نموده است. در اینجا بخاطر وجود دو نوع پارامتر Generic امکان بازگرداندن خروجی با نوع متفاوت از ورودی خواهد بود. مثلا تابع می‌تواند به شکل <Sum<int, long تعریف شود که در آن یک آرایه از نوع int دارد و خروجی از نوع long خواهد بود.

در این مثال TResult.Zero به معنی مقدار 0 به عنوان خروجی و تابع TResult.CreateChecked برای ایجاد خطای OverflowException در فرآیند تبدیل مقدار T به TResult در صورتیکه مقدار کمتر یا بیشتر از ظرفیت نوع خروجی (TResult) باشد. مثلا اگر <Sum<int,byte را در نظر بگیریم، در صورتیکه مقدار خروجی بیشتر از 255 باشد با خطای OverflowException مواجه خواهیم شد.

کد تابع Sum بصورت Generic Math
کد تابع Sum بصورت Generic Math


تابع بعدی Average است، این تابع مجموعه‌از اعداد را با هم جمع و سپس به تعداد آنها تقسیم خواهد نمود.

کد تابع Average بصورت Generic Math
کد تابع Average بصورت Generic Math

در نهایت هم تابع StandardDeviation را خواهیم داشت، این تابع با استفاده از دو تابع قبلی اقدام به محاسبه انحراف از معیار مجموع اعداد ورودی مینماید. این تابع یک قید (constraint) جدید بنام IFloatingPointIeee754 را به عنوان رابط (interface) نوع خروجی معرفی می‌کند.

اما IFloatingPointIeee754 چیست؟ این interface اعلام می‌دارد که نوع باید استاندارد IEEE 754 در مورد تعداد اعشار رعایت کند که در واقع اشاره به انواع System.Double (double) و System.Single(float) در دات‌نت دارد.

در اینجا ما با یک API جدید بنام CreateSaturating روبرو هستیم که وظیفه آن تبدیل بین انواع و مدیریت سرریز است. این تابع بخصوص در مورد انواع اعشار شناور با توجه به بازه گسترده ذخیره‌سازی آنها، بیانگر رفتار تبدیل مقادیر است و تضمین کننده ذخیره درست عدد و پیشگیری از خطای سرریز است(این رفتار وظیفه ذاتی نوع داده اعشاری است). به مانند رفتار byte.CreateSaturating که 1- را به 0 تبدیل میکند زیرا امکان ذخیره مقادیر کمتر صفر در نوع داده byte نیست یا تبدیل مقدار 256 به 255 با توجه به سقف حداکثری نوع byte.

دیگر API نیز Sqrt است و در واقع همان عملیات Math.Sqrt یا MathF.Sqrt را پیاده سازی میکند و ریشه دوم عدد را محاسبه میکند.

تابع نهایی محاسبه انحراف از معیار با استفاده از Generic Math
تابع نهایی محاسبه انحراف از معیار با استفاده از Generic Math

از این پس تمام انوع داده‌هایی که رابط (interface)های مورد نیاز را پیاده‌سازی کرده باشند امکان استفاده از این توابع را خواهند داشت(البته در دات‌نت7 و بالاتر).



علیرضا علیمردان-یک برنامه‌نویس.علاقه‌مند به حوزه فن‌آوری اطلاعات،هوافضا و ریاضیات.
شاید از این پست‌ها خوشتان بیاید