محدودیت های نوع پارامتر
constraints (محدودیت ها) کامپایلر را در مورد قابلیت هایی که نوع یک آرگومان (type argument) باید داشته باشد آگاه می کند. بدون هیچ گونه محدودیتی، type آرگومان می تواند هر نوعی باشد. کامپایلر فقط می تواند اعضای System.Object را فرض کند که کلاس پایه (base class) نهایی برای هر نوع NET. است. اگر کد کلاینت از نوعی استفاده کند که محدودیتی را برآورده نمی کند، کامپایلر یک خطا صادر می کند. محدودیت ها با استفاده از کلمه کلیدی where مشخص می شوند. این constrain ها و مفهوم آن ها در ادامه توضیح داده می شوند.
1- where T : struct
نوع آرگومان باید یک نوع مقدار غیر قابل تهی (non-nullable) باشد. از آنجایی که همه انواع مقادیر (value types)، یک سازنده بدون پارامتر قابل دسترسی دارند، محدودیت struct دلالت بر محدودیت ()new دارد و نمی تواند با محدودیت ()new ترکیب شود. شما نمی توانید محدودیت struct را با محدودیت مدیریت نشده ترکیب کنید.
2- where T : class
نوع آرگومان باید یک نوع مرجع (reference type) باشد. این محدودیت همچنین برای هر کلاس، interface ،delegate یا نوع آرایه اعمال می شود. در یک nullable context در C# 8.0 یا جدیدتر، T باید یک نوع مرجع غیر قابل تهی (non-nullable reference type) باشد.
3- where T : class?
نوع آرگومان باید یک نوع مرجع باشد، اعم از nullable یا non-nullable. این محدودیت همچنین برای هر کلاس، interface ،delegate یا نوع آرایه اعمال می شود.
4- where T : notnull
نوع آرگومان باید یک نوع غیر قابل تهی (non-nullable type) باشد. آرگومان می تواند یک نوع مرجع غیر قابل تهی در C# 8.0 یا جدیدتر یا یک نوع مقدار غیر قابل تهی باشد.
5- where T : default
این محدودیت زمانی ابهام (ambiguity) را برطرف می کند که شما نیاز به تعیین یک نوع پارامتر نامحدود زمانی که یک متد را override می کنید دارید یا یک پیاده سازی interface صریح ارائه می کنید. محدودیت default بر متد پایه بدون محدودیت class یا struct دلالت دارد.
6- where T : unmanaged
نوع آرگومان باید یک نوع unmanaged غیر قابل تهی باشد. محدودیت مدیریت نشده (unmanaged constraint) بر محدودیت struct دلالت دارد و نمی توان آن را با محدودیت های struct یا ()new ترکیب کرد.
7- ()where T : new
آرگومان type باید سازنده بدون پارامتر عمومی داشته باشد. هنگامی که همراه با سایر محدودیت ها استفاده می شود، محدودیت ()new باید در آخر مشخص شود. محدودیت ()new را نمی توان با struct و محدودیت های unmanaged ترکیب کرد.
8- <where T : <base class name
نوع آرگومان باید کلاس پایه تعیین شده باشد یا از کلاس پایه مشتق شود. در یک context از نوع nullable در C# 8.0 و جدیدتر، T باید یک نوع مرجع غیر قابل تهی باشد که از کلاس پایه مختص آن مشتق شده است.
9- ?<where T : <base class name
نوع آرگومان باید کلاس پایه تعیین شده باشد یا از کلاس پایه مشتق شود. در یک context از نوع nullable در C# 8.0 و جدیدتر، T ممکن است یک نوع nullable یا nonnullable باشد که از کلاس پایه مشخص شده مشتق شده است.
10- <where T : <interface name
نوع آرگومان باید اینترفیس تعیین شده باشد یا اینترفیس تعیین شده را پیاده سازی کند. محدودیت های interface چندگانه را می توان مشخص کرد. interface محدود کننده نیز می تواند generic باشد. در یک context از نوع nullable در C# 8.0 و جدیدتر، T باید یک نوع غیر تهی باشد که interface مشخص شده را پیاده سازی کند.
11- ?<where T : <interface name
نوع آرگومان باید اینترفیس تعیین شده باشد یا اینترفیس تعیین شده را پیاده سازی کند. محدودیت های interface چندگانه را می توان تعیین کرد. رابط محدود کننده نیز می تواند generic باشد. در یک context از نوع nullable در C# 8.0، T ممکن است یک نوع مرجع nullable، یک نوع مرجع غیر تهی، یا یک نوع مقدار باشد. T ممکن است یک نوع مقدار nullable نباشد.
12- where T : U
نوع آرگومان ارائه شده برای T باید آرگومان ارائه شده برای U باشد یا از آن مشتق شود. در یکcontext از نوع nullable، اگر U یک نوع مرجع non-nullable باشد، T باید نوع مرجع non-nullable باشد. اگر U یک نوع مرجع تهی باشد، T ممکن است تهی یا غیر قابل تهی باشد.
چرا باید از constraints استفاده کنیم؟
constraints قابلیت ها و انتظارات یک پارامتر نوع را مشخص می کند. تعریف و اعلام این محدودیت ها به این معنی است که می توانید از عملیات و فراخوانی متد از نوع محدود کننده استفاده کنید. اگر کلاس یا متد generic شما از عملیاتی فراتر از انتساب ساده یا فراخوانی هر متدی که توسط System.Object پشتیبانی نمی شود، روی اعضای generic استفاده می کند، محدودیت هایی را برای نوع پارامتر اعمال خواهید کرد. به عنوان مثال، محدودیت کلاس پایه به کامپایلر می گوید که فقط اشیاء از این نوع یا مشتق شده از این نوع به عنوان نوع آرگومان استفاده خواهند شد. هنگامی که کامپایلر این تضمین را داشته باشد، می تواند اجازه دهد تا متدهایی از آن نوع در کلاس generic فراخوانی شوند. مثال کد زیر عملکردی را نشان می دهد که می توانید با اعمال یک محدودیت کلاس پایه به کلاس <GenericList<T اضافه کنید.
constraint کلاس generic را قادر می سازد تا از ویژگی Employee.Name استفاده کند. محدودیت مشخص می کند که همه آیتم ها از نوع T تضمین شده است که یا یک شیء Employee باشند یا یک شی که از Employee به ارث می رسد باشند.
محدودیت های چندگانه را می توان برای یک نوع پارامتر اعمال کرد و خود محدودیت ها می توانند انواع generic باشند، به صورت زیر:
هنگام اعمال محدودیت where T : class از عملگرهای == و =! در نوع پارامتر اجتناب کنید زیرا این عملگرها فقط برای هویت مرجع (reference identity) تست می کنند، نه برای برابری مقدار. این رفتار حتی اگر این عملگرها در نوعی که به عنوان آرگومان استفاده می شود overload شوند، رخ می دهد. کد زیر گویای این موضوع است. خروجی false است حتی اگر کلاس String عملگر == را overload کند.
کامپایلر فقط می داند که T در زمان کامپایل یک نوع مرجع است و باید از عملگرهای default استفاده کند که برای همه انواع مرجع معتبر هستند. اگر باید برابری مقدار را آزمایش کنید، روش پیشنهادی این است که محدودیت <where T: IEquatable<T یا محدودیت <where T: IComparable<T را نیز اعمال کنید و interface را در هر کلاسی که برای ساخت کلاس generic استفاده می شود، پیاده سازی کنید.
محدودسازی multiple parameters
همانطور که در مثال زیر نشان داده شده است، می توانید محدودیت ها را برای چندین پارامتر، و محدودیت های متعدد را برای فقط یک پارامتر اعمال کنید:
نوع پارامترهای نامحدود (unbounded type parameters)
نوع پارامترهایی که هیچ محدودیتی ندارند، مانند T در کلاس عمومی {}<SampleClass<T، پارامترهای نوع نامحدود نامیده می شوند. نوع پارامترهای نامحدود قوانین زیر را دارند:
1- عملگرهای != و == را نمی توان استفاده کرد زیرا هیچ تضمینی وجود ندارد که نوع آرگومان concrete از این عملگرها پشتیبانی کند.
2- آن ها را می توان به و از System.Object تبدیل کرد یا به طور صریح به هر نوع اینترفیسی تبدیل کرد.
3- می توانید آن ها را با null مقایسه کنید. اگر یک پارامتر نامحدود با null مقایسه شود، اگر نوع آرگومان یک نوع مقدار باشد، مقایسه همیشه false را برمی گرداند.
نوع پارامترها به عنوان محدودیت (type parameters as constraints)
استفاده از یک نوع پارامتر generic به عنوان یک محدودیت زمانی مفید است که یک تابع عضو با نوع پارامتر خاص خود باید آن پارامتر را به type parameter نوع حاوی محدود کند، همانطور که در مثال زیر نشان داده شده است:
در مثال قبلی،T یک محدودیت نوع در context متد Add، و یک نوع پارامتر نامحدود در context کلاس List است.
نوع پارامترها همچنین می توانند به عنوان محدودیت در تعاریف کلاس generic استفاده شوند. نوع پارامترها باید در داخل براکت همراه با هر نوع پارامتر دیگری تعریف شود:
سودمندی نوع پارامترها به عنوان constraints با کلاس های generic محدود است، زیرا کامپایلر نمی تواند چیزی در مورد نوع پارامتر فرض کند جز اینکه از System.Object مشتق شده است. از نوع پارامترها به عنوان constraints در کلاس های generic در سناریوهایی استفاده کنید که در آن ها می خواهید یک رابطه ارثی بین دو نوع پارامتر اعمال کنید.
notnull constraint
با شروع C# 8.0، می توانید از محدودیت notnull برای تعیین اینکه نوع آرگومان باید یک نوع مقدار غیر قابل تهی یا نوع مرجع غیر قابل تهی باشد استفاده کنید. برخلاف اکثر محدودیت های دیگر، اگر یک نوع آرگومان، محدودیت notnull را نقض کند، کامپایلر به جای خطا، یک هشدار ایجاد می کند.
محدودیت notnull تنها زمانی اثر می گذارد که در یک context از نوع nullable استفاده شود. اگر محدودیت notnull را در یک context از نوع nullable oblivious اضافه کنید، کامپایلر هیچ هشدار یا خطایی برای نقض محدودیت ایجاد نمی کند.
محدودیت کلاس (class constraint)
با شروع C# 8.0، محدودیت کلاس در یک context از نوع nullable مشخص می کند که نوع آرگومان باید یک نوع مرجع غیر تهی باشد. در یک context از نوع nullable، زمانی که یک نوع آرگومان یک نوع مرجع nullable باشد، کامپایلر یک هشدار ایجاد می کند.
محدودیت پیش فرض (default constraint)
افزودن انواع مرجع nullable ، استفاده از T را پیچیده می کند؟ در یک نوع یا متد generic قبل از C# 8، T؟ تنها زمانی می توان از آن استفاده کرد که محدودیت struct به T اعمال شود. در آن context ، T ؟ به نوع <Nullable<T برای T اشاره دارد. شروع با C# 8، T ؟ می تواند با محدودیت struct یا محدودیت class استفاده شود، اما یکی از آن ها باید ارائه شوند. چه زمانی از محدودیت کلاس استفاده شد، ؟T به نوع مرجع nullable برای T اشاره شده است. شروع با C# 9، T ؟ زمانی می توان از آن استفاده کرد که هیچ یک از محدودیت ها اعمال نشود. در آن صورت، ?T برای انواع مقادیر و انواع مرجع همانند C# 8 تفسیر می شود. با این حال، اگر T یک نمونه از <Nullable<T باشد، ؟T همان T است. به عبارت دیگر T نمی شود؟؟.
از آنجایی که اکنون از ?T می توان بدون محدودیت کلاس یا ساختار استفاده کرد، ابهامات ممکن است درoverrides یا پیاده سازی های صریح interface ایجاد شود. در هر دو مورد، override محدودیت ها را شامل نمی شود، اما آنها را از کلاس پایه به ارث می برد. هنگامی که کلاس پایه محدودیت کلاس یا ساختار را اعمال نمی کند، کلاس های مشتق شده باید به نحوی مشخص کنند که یک override برای متد پایه بدون هیچ محدودیتی اعمال شود. این زمانی است که متد مشتق شده محدودیت default را اعمال می کند. محدودیت default نه محدودیت کلاس و نه ساختار را واضح نمی کند.
محدودیت مدیریت نشده (unmanaged constraint)
با شروع C# 7.3، می توانید از محدودیت مدیریت نشده برای تعیین اینکه نوع پارامتر باید یک نوع مدیریت نشده non-nullable باشد، استفاده کنید. محدودیت مدیریت نشده شما را قادر می سازد تا روال های قابل استفاده مجدد را بنویسید تا با انواعی که می توانند به عنوان بلوک های حافظه دستکاری شوند. همانطور که در مثال زیر نشان داده شده است:
متد قبلی باید در یک context ناامن کامپایل شود زیرا از عملگر sizeof بر روی نوعی استفاده می کند که به عنوان نوع built-in شناخته شده نیست. بدون محدودیت unmanaged، عملگر sizeof در دسترس نیست.
unmanaged constraint بر محدودیت struct دلالت دارد و نمی توان با آن ترکیب کرد. از آنجایی که محدودیت struct بر محدودیت ()new دلالت دارد، محدودیت مدیریت نشده را نمی توان با محدودیت ()new نیز ترکیب کرد.
Delegate constraints
همچنین با شروع C# 7.3، می توانید از System.Delegate یا System.MulticastDelegate به عنوان یک محدودیت کلاس پایه استفاده کنید. CLR همیشه این محدودیت را مجاز می دانست، اما زبان برنامه نویسی سی شارپ آن را مجاز نمی دانست. محدودیت System.Delegate شما را قادر می سازد کدی بنویسید که با delegates به شیوه ای ایمن کار کند. کد زیر یک متد extension را تعریف می کند که دو delegates را با هم ترکیب می کند به شرطی که از یک نوع باشند:
می توانید از متد بالا برای ترکیبdelegate هایی که از همان نوع هستند استفاده کنید:
اگر خط آخر را از حالت کامنت خارج کنید، کامپایل نمی شود. هر دو نوع اول و تست، انواع delegate هستند، اما انواع مختلف delegate هستند.
محدودیت های Enum
با شروع از C# 7.3، همچنین می توانید نوع System.Enum را به عنوان یک محدودیت کلاس پایه تعیین کنید. CLR همیشه این محدودیت را مجاز می دانست، اما زبان برنامه نویسی سی شارپ آن را مجاز نمی دانست. ژنریک هایی که از System.Enum استفاده می کنند، برنامه نویسی که از لحاظ type ایمن است را برای ذخیره نتایج حاصل از استفاده از متد های استاتیک در System.Enum ارائه می دهند. نمونه زیر تمام مقادیر معتبر یک نوع enum را پیدا می کند، و سپس یک فرهنگ لغت می سازد که آن مقادیر را به نمایش رشته ای خود نگاشت می کند.
Enum.GetValues و Enum.GetName از reflection استفاده می کنند که پیامدهای عملکردی دارد. می توانید EnumNamedValues را فراخوانی کنید تا مجموعه ای بسازید که به جای تکرار فراخوانی هایی که نیاز به reflection دارند، ذخیره شده و مجدداً استفاده شوند.
می توانید همانطور که در نمونه زیر نشان داده شده است برای ایجاد یک enum و ایجاد فرهنگ لغت از مقادیر و نام های آن استفاده کنید: