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