مقدمه

در درس‌های گذشته دیدیم که فراداده (Metadata) اطلاعات توصیفی در مورد کد ما را در اسمبلی ذخیره می‌کند. اما اگر بخواهیم اطلاعات توصیفی بیشتری را به کد خود اضافه کنیم چه؟ اطلاعاتی که شاید مستقیماً بر اجرای کد تأثیر نگذارند، اما برای ابزارهای دیگر، فریم‌ورک‌ها یا حتی کد خود ما در زمان اجرا، حاوی اطلاعات مفیدی باشند. C# برای این منظور یک مکانیزم قدرتمند و اعلانی به نام صفت‌ها (Attributes) را فراهم کرده است. یک صفت، راهی برای الصاق فراداده‌ی سفارشی به عناصر مختلف کد مانند اسمبلی‌ها، کلاس‌ها، متدها، پراپرتی‌ها و ... است. این اطلاعات اضافی سپس می‌توانند در زمان اجرا با استفاده از انعکاس (Reflection) خوانده و مورد استفاده قرار گیرند.

صفت (Attribute) چیست؟

یک صفت، کلاسی است که از کلاس پایه‌ی System.Attribute ارث‌بری می‌کند. ما با استفاده از یک سینتکس خاص (قرار دادن نام کلاس صفت در داخل براکت [] قبل از یک عنصر کد)، یک نمونه از آن کلاس صفت را به آن عنصر "متصل" می‌کنیم.

ما قبلاً با برخی از صفت‌های استاندارد .NET برخورد داشته‌ایم، حتی اگر به نام آن‌ها توجه نکرده باشیم. برای مثال:

  • [Serializable]: به CLR می‌گوید که یک کلاس می‌تواند به یک جریان از بایت‌ها تبدیل شود.
  • [Obsolete]: به کامپایلر می‌گوید که هنگام استفاده از یک متد یا کلاس، یک هشدار یا خطا مبنی بر منسوخ شدن آن نمایش دهد.
  • [TestMethod]: در فریم‌ورک‌های تست واحد، این صفت یک متد را به عنوان یک متد تست علامت‌گذاری می‌کند.
Copy Icon Program.cs
// Applying the ObsoleteAttribute to a method.
[Obsolete("This method is outdated. Use NewMethod instead.")]
public void OldMethod() { /* ... */ }

در اینجا، ما فراداده‌ی اضافی (اینکه این متد منسوخ است) را به متد OldMethod متصل کرده‌ایم. اکنون، هر کدی که سعی در فراخوانی این متد داشته باشد، یک هشدار در زمان کامپایل دریافت خواهد کرد.

ساخت یک صفت سفارشی

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

قدم اول: تعریف کلاس صفت

برای تعریف یک صفت، یک کلاس جدید می‌سازیم که از System.Attribute ارث‌بری کند. طبق قرارداد، نام این کلاس‌ها باید به کلمه‌ی "Attribute" ختم شود.

بیایید یک صفت به نام HelpAttribute بسازیم که به ما اجازه دهد یک متن راهنما را به کلاس‌ها و متدهایمان ضمیمه کنیم.

Copy Icon HelpAttribute.cs
// By convention, custom attribute classes end with "Attribute".
public class HelpAttribute : Attribute
{
    public string Topic { get; }
    public string Description { get; set; }

    public HelpAttribute(string topic)
    {
        Topic = topic;
    }
}

قدم دوم: اعمال صفت

اکنون می‌توانیم از این صفت بر روی عناصر کد خود استفاده کنیم. هنگام اعمال یک صفت، می‌توانیم کلمه‌ی "Attribute" را از انتهای نام آن حذف کنیم.

Copy Icon MyMath.cs
[Help("Math Functions", Description = "A class for basic math operations.")]
public class MyMath
{
    [Help("Addition")]
    public int Add(int a, int b) => a + b;
}

در اینجا، ما به صورت اعلانی، فراداده‌ی راهنما را به کلاس MyMath و متد Add آن اضافه کرده‌ایم. این اطلاعات اکنون بخشی از فراداده‌ی اسمبلی کامپایل‌شده هستند.

خواندن صفت‌ها در زمان اجرا با انعکاس

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

Copy Icon Program.cs
Type mathType = typeof(MyMath);

// Get custom attributes applied to the class.
object[] classAttributes = mathType.GetCustomAttributes(false);
foreach (object attr in classAttributes)
{
    if (attr is HelpAttribute help)
    {
        Console.WriteLine($"Help for class {mathType.Name}:");
        Console.WriteLine($"  Topic: {help.Topic}, Desc: {help.Description}");
    }
}

// Get custom attributes for a specific method.
MethodInfo addMethod = mathType.GetMethod("Add");
object[] methodAttributes = addMethod.GetCustomAttributes(false);
// ... and so on ...

این الگو (تعریف صفت، اعمال آن، و سپس خواندن آن با انعکاس) اساس کار بسیاری از فریم‌ورک‌های مدرن است. این به توسعه‌دهندگان فریم‌ورک اجازه می‌دهد تا یک API فراهم کنند که کاربران آن بتوانند با استفاده از صفت‌ها، رفتار فریم‌ورک را به صورت اعلانی کنترل کنند، بدون اینکه نیاز به نوشتن کدهای پیچیده داشته باشند.