اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
سه دقیقه
هر کلاسی در #C که از کلاس پایهی System.Attribute مشتق شود، یک Attribute نامیده میشود و مهمترین و هدف و کاربرد آنها، مزین کردن و علامتگذاری سایر نوعها و فیلدها هستند تا بر اساس آنها بتوان کارکردهای بیشتری را در اختیار آن نوعها قرار داد. برای مثال، استفاده از ویژگیهای JsonProperty و یا JsonPropertyName در حین اعمال serializations و یا در کاربردهای اعتبارسنجی مانند ویژگیهای Required، Range و امثال آنها:
روش متداول ارسال نوعها به attributes تا پیش از C# 11
تا پیش از C# 11، روش پیاده سازی یک attribute جنریک که بتواند با انواع و اقسام نوعها کار کند، به صورت زیر است:
- ارسال یک پارامتر از نوع System.Type به سازندهی attribute
- تعریف خاصیتی مانند ParamType در صورت نیاز؛ تا مشخص کند که چه نوعی به سازندهی attribute ارسال شدهاست. مانند مثال فرضی زیر:
و سپس با استفاده از عملگر typeof، نوع مدنظر را به سازندهی ویژگی تعریف شده، ارسال میکنیم:
یک نمونه مثال دنیای واقعی آن، [ServiceFilter(typeof(ResponseLoggerFilter))] در ASP.NET Core است که دیگر با وجود جنریکها، آنچنان هماهنگ و یکدست با سایر اجزای زبان به نظر نمیرسد. نمونهی هماهنگ با پیشرفتهای زبان، باید چنین شکلی را داشته باشد: [<ServiceFilter<ResponseLoggerFilter]
امکان تعریف ویژگیهای جنریک در C# 11
C# 11 به همراه پیشتیبانی از generic attributes ارائه شدهاست. بنابراین اینبار بجای ارسال پارمتری از نوع Type به سازندهی ویژگی، میتوان کلاس آن attribute را به صورت جنریک تعریف کنیم که میتواند یک یا چندین نوع را به عنوان پارامتر بپذیرد. بنابراین مثال قبل در C# 11 به صورت زیر بازنویسی میشود:
یک مزیت مهم این روش نسبت به قبل، امکان تعریف قیود و type safety است. برای نمونه در مثال فوق، نوع T به کلاسها محدود شدهاست و نوعهای دیگر را نمیپذیرد. به این ترتیب میتوان این نوع بررسیها را بجای زمان اجرا و صدور استثناءها، دقیقا در زمان کامپایل انجام داد.
و اگر نیاز به تعیین چند نوع بود، باید خاصیت AllowMultiple نحوهی استفاده از ویژگی را به true تنظیم کرد:
تا بتوان به تعریف زیر رسید:
محدودیتهای انتخاب نوعها در ویژگیهای جنریک C# 11
در ویژگیهای جنریک نمیتوان از نوعهای زیر استفاده کرد (همان محدودیتهای typeof، در اینجا هم برقرار هستند):
- نوعهای dynamic
- nullable reference types مانند ?string
- نوعهای tuple تعریف شدهی به کمک C# tuple syntax مانند (int x, int y)
چون این نوعها به همراه یکسری metadata annotations هستند که صرفا بیانگر توضیحی اضافی در مورد نوع بکارگرفته شده هستند و در صورت نیاز، بجای آنها میتوانید از نوعهای زیر استفاده کنید:
- از object بجای dynamic
- از string بجای ?string
- از <ValueTuple<int, int بجای (int X, int Y)
همچنین در زمان استفادهی از یک ویژگی جنریک، باید نوع مورد استفاده، کاملا مشخص و در اصطلاح fully constructed باشد:
public class Student { [JsonPropertyName("id")] public int Id { get; set; } [JsonPropertyName("name")] public string Name { get; set; } } public class WeatherForecast { [Required] public int TemperatureC { get; set; } [MinLength(50)] public string Summary { get; set; } }
روش متداول ارسال نوعها به attributes تا پیش از C# 11
تا پیش از C# 11، روش پیاده سازی یک attribute جنریک که بتواند با انواع و اقسام نوعها کار کند، به صورت زیر است:
- ارسال یک پارامتر از نوع System.Type به سازندهی attribute
- تعریف خاصیتی مانند ParamType در صورت نیاز؛ تا مشخص کند که چه نوعی به سازندهی attribute ارسال شدهاست. مانند مثال فرضی زیر:
[AttributeUsage(AttributeTargets.Class)] public class CustomDoNothingAttribute: Attribute { // Note the type parameter in the constructor public CustomDoNothingAttribute(Type t) { ParamType = t; } public Type ParamType { get; } }
[CustomDoNothing(typeof(string))] public class Student { public int Id { get; set; } public string Name { get; set; } }
امکان تعریف ویژگیهای جنریک در C# 11
C# 11 به همراه پیشتیبانی از generic attributes ارائه شدهاست. بنابراین اینبار بجای ارسال پارمتری از نوع Type به سازندهی ویژگی، میتوان کلاس آن attribute را به صورت جنریک تعریف کنیم که میتواند یک یا چندین نوع را به عنوان پارامتر بپذیرد. بنابراین مثال قبل در C# 11 به صورت زیر بازنویسی میشود:
[AttributeUsage(AttributeTargets.Class)] public class CustomDoNothingAttribute<T> : Attribute where T : class { public T ParamType { get; } } [CustomDoNothing<string>] public class Student { public int Id { get; set; } public string Name { get; set; } }
و اگر نیاز به تعیین چند نوع بود، باید خاصیت AllowMultiple نحوهی استفاده از ویژگی را به true تنظیم کرد:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class DecorateAttribute<T> : Attribute where T : class { // .... }
[Decorate<LoggerDecorator>] [Decorate<TimerDecorator>] public class SimpleWorker { // .... }
محدودیتهای انتخاب نوعها در ویژگیهای جنریک C# 11
در ویژگیهای جنریک نمیتوان از نوعهای زیر استفاده کرد (همان محدودیتهای typeof، در اینجا هم برقرار هستند):
- نوعهای dynamic
- nullable reference types مانند ?string
- نوعهای tuple تعریف شدهی به کمک C# tuple syntax مانند (int x, int y)
چون این نوعها به همراه یکسری metadata annotations هستند که صرفا بیانگر توضیحی اضافی در مورد نوع بکارگرفته شده هستند و در صورت نیاز، بجای آنها میتوانید از نوعهای زیر استفاده کنید:
- از object بجای dynamic
- از string بجای ?string
- از <ValueTuple<int, int بجای (int X, int Y)
همچنین در زمان استفادهی از یک ویژگی جنریک، باید نوع مورد استفاده، کاملا مشخص و در اصطلاح fully constructed باشد:
public class GenericAttribute<T> : Attribute { } public class GenericType<T> { [GenericAttribute<T>] // Not allowed! generic attributes must be fully constructed types. public string Method1() => default; [GenericAttribute<string>] public string Method2() => default; }