اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
پنج دقیقه
امکان داشتن اعضای static abstract در اینترفیسها شاید عجیب بهنظر برسد یا حتی غیرضروری؛ اما در C# 11، پایهی قابلیت جدیدی به نام «ریاضیات جنریک» شدهاست. به همین جهت در ابتدا نیاز است با اعضای static abstract آشنا شد و در قسمتی دیگر به «ریاضیات جنریک» پرداخت.
مثالی جهت توضیح علت نیاز به اعضای static abstract در اینترفیسها
فرض کنید قصد داریم حاصل جمع اعضای یک آرایهی int را محاسبه کنیم:
روش متداول اینکار را در اینجا ملاحظه میکنید که حلقهای بر روی عناصر آرایه، جهت یافتن حاصل جمع آنها تشکیل شدهاست. اکنون فرض کنید بجای آرایهای که در متد Test استفاده شده، از آرایهی زیر استفاده شود:
اینبار با خطای زیر متوقف میشویم:
عنوان میکند که آرایهی مدنظر از نوع []double تشخیص داده شدهاست و متد AddAll، تنها آرایههای از نوع int را قبول میکند. در جهت رفع این مشکل شاید بهتر باشد نمونهی جنریک متد AddAll را ایجاد کنیم، تا بتوان انواع و اقسام نوعهای ممکن را به آن ارسال کرد:
اما اینکار میسر نیست. چون زمانیکه از T استفاده میشود، مفهوم و امکان وجود «عدد صفر» در آن نوع، مشخص نیست. یک روش حل این مشکل، مقید و محدود کردن نوع T است. برای مثال عنوان کنیم که T، عددی است و از نوع INumber (فرضی/خیالی) است و این INumber فرضی، به همراه مفهوم عدد صفر هم هست. یعنی اولین سطر بدنهی متد AddAll را باید بتوان به صورت زیر بازنویسی کرد:
یعنی باید بتوان از طریق یک «نوع» عمومی مانند T (نه وهلهای/نمونهای/instance ای از آن نوع؛ دقیقا خود آن نوع) به خاصیت Zero آن نوع، دسترسی یافت و آن خاصیت هم باید از نوع استاتیک باشد و چون تا C# 10 و دات نت 6، چنین امکانی مهیا نشده بود (البته در حالت preview قرار داشت)، تنها راه ممکن، تهیهی یک نمونهی جدید double متد AddAll است/بود.
در C# 11 و دات نت 7، با معرفی اینترفیس جدید INumber، میتوان قید <where T : INumber<T را به T اعمال کرد (مانند نمونهی زیر) و همچنین با استفاده از اعضای static abstract این اینترفیس، به مقدار T.Zero هم دسترسی یافت و اینبار قطعه کد زیر، بدون مشکل در C# 11 کامپایل میشود:
اگر به تعاریف INumber جدید مراجعه کنیم، نه فقط به خواص abstract static جدیدی میرسیم (که امکان دسترسی به T.Zero را میسر کردهاند)،
بلکه امکان تعریف اپراتورهای abstract static هم میسر شدهاند (به همین جهت است که در کدهای فوق سطر result += value، هنوز هم کار میکند):
مثال دیگری از کاربرد اعضای abstract static در اینترفیسها
فرض کنید اینترفیس ISport را به همراه دو پیاده سازی از آن، به صورت زیر تعریف کردهایم:
اکنون جهت کار با متد IsTeamSport و تعریف جنریک این متد، میتوان به صورت متداول زیر عمل کرد که در آن T، مقید به ISport شدهاست:
برای کار با آن هم باید حتما نمونهای از ()new Football و یا ()new Swimming را به آن ارسال کرد:
سؤال: آیا با توجه به مشخص بودن و محدود بودن نوع T، میتوان با حذف پارامتر T sport، به متد IsTeamSport اینترفیس ISport دسترسی یافت؟ یعنی تعریف متد Display را طوری تغییر داد تا دیگر نیاز به نمونه سازی ()new Football نداشته باشد. همینقدر که نوع Football مشخص بود، بتوان متد IsTeamSport آنرا فراخوانی کرد.
پاسخ: تا پیشاز C# 11 یکی از روشهای انجام اینکار، استفاده از reflection بود. اما در C# 11 با کمک static abstractها میتوان تعاریف این اینترفیس و پیاده سازیهای آنرا به صورت زیر تغییر داد:
تا اینبار جهت دسترسی به متد IsTeamSport،مستقیما بتوان به خود «نوع»، «بدون نیاز به نمونه سازی آن» مراجعه کرد و قطعه کد زیر در C# 11 معتبر است:
مثالی جهت توضیح علت نیاز به اعضای static abstract در اینترفیسها
فرض کنید قصد داریم حاصل جمع اعضای یک آرایهی int را محاسبه کنیم:
namespace CS11Tests; public class StaticAbstractMembers { public static void Test() { var sum = AddAll(new[] { 1, 2, 3, 4 }); Console.WriteLine(sum); } private static int AddAll(int[] values) { int result = 0; foreach (var value in values) { result += value; } return result; } }
var sum = AddAll(new[] { 1, 2, 3, 4, 0.68 });
Argument 1: cannot convert from 'double[]' to 'int[]' [CS11Tests]csharp(CS1503)
private static T AddAll<T>(T[] values) { T result = 0; foreach (var value in values) { result += value; } return result; }
T result = T.Zero;
در C# 11 و دات نت 7، با معرفی اینترفیس جدید INumber، میتوان قید <where T : INumber<T را به T اعمال کرد (مانند نمونهی زیر) و همچنین با استفاده از اعضای static abstract این اینترفیس، به مقدار T.Zero هم دسترسی یافت و اینبار قطعه کد زیر، بدون مشکل در C# 11 کامپایل میشود:
using System.Numerics; namespace CS11Tests; public class StaticAbstractMembers { public static void Test() { //var sum = AddAll(new[] { 1, 2, 3, 4 }); var sum = AddAll(new[] { 1, 2, 3, 4, 0.68 }); Console.WriteLine(sum); } private static T AddAll<T>(T[] values) where T : INumber<T> { T result = T.Zero; foreach (var value in values) { result += value; } return result; } }
abstract static TSelf One { get; } abstract static TSelf Zero { get; }
abstract static TResult operator +(TSelf left, TOther right);
مثال دیگری از کاربرد اعضای abstract static در اینترفیسها
فرض کنید اینترفیس ISport را به همراه دو پیاده سازی از آن، به صورت زیر تعریف کردهایم:
public interface ISport { bool IsTeamSport(); } public class Swimming : ISport { public bool IsTeamSport() => false; } public class Football : ISport { public bool IsTeamSport() => true; }
public class StaticAbstractMembers { public static void Display<T>(T sport) where T : ISport { Console.WriteLine("Is Team Sport:" + sport.IsTeamSport()); } }
Display(new Football());
پاسخ: تا پیشاز C# 11 یکی از روشهای انجام اینکار، استفاده از reflection بود. اما در C# 11 با کمک static abstractها میتوان تعاریف این اینترفیس و پیاده سازیهای آنرا به صورت زیر تغییر داد:
public interface ISport { static abstract bool IsTeamSport(); } public class Swimming : ISport { public static bool IsTeamSport() => false; } public class Football : ISport { public static bool IsTeamSport() => true; }
public class StaticAbstractMembers { public static void Display<T>() where T : ISport { Console.WriteLine("Is Team Sport:" + T.IsTeamSport()); } }