حسین احمدی
بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب

آموزش جامع Reflection در سی شارپ

در برنامه هایی که مبتنی بر .NET نوشته می شوند، هر برنامه نوشته شده بعد از Compile شدن، تبدیل به یک فایل dll یا exe می شود. به این فایل های خروجی اصطلاحاً Assembly می گویند. در حقیقت Assembly ها، Package هایی هستند که شامل اطلاعات مربوط به کدهای نوشته شده برای برنامه هستند. هر اسمبلی به صورت پیش فرض شامل بخش های زیر است:

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران
سرفصل های این مطلب
  1. Meta Data Viewer
  2. Late Binding
  3. Attibute ها
  1. Assembly Manifest: توصیفات اسمبلی، هر اسمبلی بعد از ایجاد شامل یکسری توضیحات است که آن را توصیف می کند. مانند نام اسمبلی، نسخه اسمبلی و ...
  2. Type Metadata: اطلاعات نوع های داده موجود در اسمبلی، برای مثال فرض کنید شما در برنامه خود یک کلاس ایجاد کرده اید. این کلاس شامل یکسری توصیفات است، برای مثال نام کلاس، سطح دسترسی به کلاس، اعضاء کلاس و حال هر یک از این اعضاء خود شامل یکسری Meta Data هستند که آن عضو را توصیف می کنند، برای مثال یک متد می تواند شامل اطلاعاتی مانند نام متد، نوع داده بازگشتی و پارامتر های متد باشد.
  3. کد IL: هر کدی که در زبان سی شارپ نوشته می شود، بعد از Compile شدن به کد IL یا Intermediate Langauge که یک زبان میانی است تبدیل می شود. اسمبلی ها همچنین شامل کد IL هستند که زمان اجرا توسط JIT یا Just In Time Compiler که یکی از سرویس های CLR است به کد ماشین تبدیل شده و اجرا می شوند.


اما هدف از این توضیحات چه بود؟ در زبان سی شارپ و کلاً هر زبان مبتنی بر دات نت می توان بوسیله قابلیتی به نام Reflection، اطلاعات و Meta Data های مربوط به برنامه ها را خواند و از آن ها استفاده کرد. در ابتدا با کلاس Type، کلمه کلیدی typeof و متد GetType می خواهیم آشنا شویم. کلاس Type، در حقیقت کلاسی است که اطلاعات مربوط به یک Data Type را به ما ارائه می دهد. فرض کنید کلاسی به صورت زیر تعریف کرده ایم:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}


حال به دو صورت می توان Meta Data یا همان اطلاعات مربوط به این کلاس را به دست آورد. شیوه اول استفاده از دستور GetType است. این دستور در کلاس Object تعریف شده و به همین دلیل در تمامی شئ های ایجاد شده در برنامه ها قابل دسترس است. خروجی این دستور شئ ای از نوع Type است. در کد زیر Type مربوط به شئ ای از کلاس Person را به دست آورده و نام کلاس را در خروجی چاپ می کنیم:

Person person = new Person();
var type = person.GetType();

Console.WriteLine(type.Name);


کد بالا، Person را در خروجی چاپ می کند. همچنین می توان به اطلاعات دیگری نیز دسترسی داشت:

Person person = new Person();
var type = person.GetType();

Console.WriteLine(type.Name);
Console.WriteLine(type.Namespace);
Console.WriteLine(type.IsPublic);
Console.WriteLine(type.IsAbstract);
Console.WriteLine(type.IsGenericType);


همانطور که گفتیم علاوه بر دستور GetType می توان از کلمه کلیدی typeof نیز برای بدست آوردن اطلاعات یک Data Type استفاده کرد، تفاوت typeof با GetType در این است که زمان استفاده از GetType می بایست ابتدا شئ ای از روی کلاس ساخته شده و بعد متد GetType فراخوانی شود، اما کلمه کلیدی typeof به صورت مستقیم با خود Data Type کار می کند:

var type = typeof (Person);

Console.WriteLine(type.Name);
Console.WriteLine(type.Namespace);
Console.WriteLine(type.IsPublic);
Console.WriteLine(type.IsAbstract);
Console.WriteLine(type.IsGenericType);


تا اینجا، با نحوه بدست آوردن Type یک کلاس آشنا شدیم. عملیات های بالا را بر روی Struct ها و Delegate ها نیز انجام دهید. یعنی امکان بدست آوردنType هر Data Type ای در دات نت وجود دارد. اما به دست آوردن Type به تنهایی فایده ای برای ما ندارد. ما باید بتوانیم استفاده های دیگری نیز از آن بکنیم. برای مثال، بعد از بدست آوردن Type کلاس Person، می توان لیست خصوصیات آن را بدست آورده و در خروجی نمایش داد. به مثال زیر دقت کنید:

var type = typeof (Person);

Console.WriteLine("Data Type Name: " + type.Name);

var properties = type.GetProperties();

Console.WriteLine("Properties:");
foreach (PropertyInfo propertyInfo in properties)
{
    Console.WriteLine(propertyInfo.Name + ": " + propertyInfo.PropertyType.Name);
}

Console.ReadKey();


در کد بالا، ابتدا Type کلاس Person را بدست آوردیم. در قسمت بعدی در خروجی نام آن را نمایش دادیم. در ادامه لیست خصوصیات کلاس را با دستور GetProperties بدست آوردیم. نوع خروجی این متد آرایه ای از PropertyInfo است. PropertyInfo اطلاعات مربوط به خصوصیت را در خود نگهداری می کند. در ادامه با دستور foreach لیست خصوصیات بدست آمده را به همراه نوع داده آن ها در خروجی چاپ کردیم.

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

Meta Data Viewer

در قسمت اول این آموزش، با نحوه بدست آوردن اطلاعات Type ها آشنا شدیم و متد GetType و کلمه کلیدی typeof را بررسی کردیم. در این قسمت می خواهیم با نوشتن یک برنامه ساده با مکانیزم های مختلف بدست آوردن اطلاعات کلاس ها و همینطور اعضاء آن آشنا شویم. برای شروع کلاسی به صورت زیر تعریف می کنیم:

public class Person
{
    public Person(int id, string firstName, string lastName)
    {
        Id = id;
        FirstName = firstName;
        LastName = lastName;
    }

    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string FullName
    {
        get { return string.Format("{0} {1}", FirstName, LastName); }
    }

    public void DisplayFullName()
    {
        Console.WriteLine("{0} {1}", FirstName, LastName);
    }
}


کلاس تعریف شده یک سازنده دارد که سه پارامتر به عنوان ورودی دریافت می کند، سه خصوصیت با نام های Id و FirstName و LastName و همینطور یک خصوصیت با نام FullName که ReadOnly است بعلاوه یک متد با نام DisplayFullName که نام کامل شخص را در خروجی نمایش می دهد. می خواهیم بوسیله Reflection اطلاعات کاملی از این کلاس را در خروجی نمایش دهیم.

Console.WriteLine("Metadata Viewer:");
Console.WriteLine("-------------------------");
var type = typeof (Person);

Console.WriteLine("Namespace: {0}", type.Namespace);
Console.WriteLine("Assembly Name: {0}", type.Assembly.FullName);
Console.WriteLine("Data Type Name: {0}", type.Name);
Console.WriteLine("Reference/Value Type: {0}", type.IsValueType ? "Value Type" : "Reference Type");    


در مرحله بعدی اطلاعات مربوط به سازنده های کلاس را می خواهیم نمایش دهیم، کد زیر را در ادامه کد متد Main می نویسیم:

var constructors = type.GetConstructors();
Console.WriteLine();
Console.WriteLine("Constructors:");
Console.WriteLine("------------------------");
foreach (ConstructorInfo constructorInfo in constructors)
{
    List parameters = new List();
    foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters())
    {
        parameters.Add(string.Format("{0} {1}", parameterInfo.ParameterType.Name, parameterInfo.Name));
    }
    Console.Write("{0}({1})", type.Name, string.Join(",", parameters));
    Console.WriteLine();
}


در کد بالا ابتدا لیست سازنده های Type را با دستور GetConstructors بدست آوردیم. نوع بازگشتی این متد آرایه ای از ConstructorInfo می باشد که اطلاعات مربوط به هر سازنده را در خود نگه می دارد. در ادامه با دستور foreach لیست سازنده ها را پیمایش کرده و آن ها را در خروجی نمایش می دهیم. دقت کنید در داخل حلقه foreach لیست پارامترهای مربوط به سازنده را توسط دستور GetParameters بدست آوردیم که آرایه ای از نوع ParameterInfo بر میگرداند. این کلاس اطلاعات مربوط به پارامتر را به ما می دهد. داخل لیستی از رشته ها، اطلاعات مربوط به پارامتر را که شامل نوع داده پارامتر و نام آن است ذخیره می کنیم. برای بدست آوردن نوع داده پارامتر کافیست از خصوصیت ParameterType که در کلاس ParameterInfo تعریف شده استفاده کنیم. در انتها نام سازنده به همراه پارامترهای آن را در خروجی در قالب زیر چاپ می شود:

Person(Int32 id,String firstName,String lastName)


در قدم بعدی می خواهیم لیست متدهای کلاس را پیدا کنیم. برای اینکار کد زیر را در ادامه کد قبل اضافه کنید:

var methods = type.GetMethods();
Console.WriteLine();
Console.WriteLine("Methods:");
Console.WriteLine("------------------------");
foreach (MethodInfo methodInfo in methods)
{
    List parameters = new List();
    foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
    {
        parameters.Add(string.Format("{0} {1}", parameterInfo.ParameterType.Name, parameterInfo.Name));
    }
    Console.Write("{0} {1}({2})",methodInfo.ReturnType.Name, methodInfo.Name, string.Join(",", parameters));
    Console.WriteLine();
}


دستور GetMethods آرایه ای از نوع MethodInfo بر می گرداند که این کلاس شامل اطلاعات مربوط به متد تعریف شده می باشد. با اجرای کد بالا، خروجی زیر نمایش داده می شود:

Methods:
------------------------
Int32 get_Id()
Void set_Id(Int32 value)
String get_FirstName()
Void set_FirstName(String value)
String get_LastName()
Void set_LastName(String value)
String get_FullName()
Void DisplayFullName()
String ToString()
Boolean Equals(Object obj)
Int32 GetHashCode()
Type GetType()


لیستی از متدها برای ما نمایش داده شدند، اما دقت کنید، متدهای با نام getId و setId یا getFirstName و setFirstName هم در لیست وجود دارند. دلیل این موضوع نحوه رفتار کامپایلر با خصوصیات است. زمان کامپایل خصوصیات تعریف شده به متدهایی تبدیل می شوند که برای قسمت های get و set می باشند. برای عدم نمایش این متدها در لیست کد بالا را به صورت زیر تغییر می دهیم:

var methods = type.GetMethods();
Console.WriteLine();
Console.WriteLine("Methods:");
Console.WriteLine("------------------------");
foreach (MethodInfo methodInfo in methods)
{
    if(methodInfo.IsSpecialName)
        continue;
    List parameters = new List();
    foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
    {
        parameters.Add(string.Format("{0} {1}", parameterInfo.ParameterType.Name, parameterInfo.Name));
    }
    Console.Write("{0} {1}({2})",methodInfo.ReturnType.Name, methodInfo.Name, string.Join(",", parameters));
    Console.WriteLine();
}


دقت کنید، کد زیر به بدنه foreach اضافه شده است:

if(methodInfo.IsSpecialName)
    continue;


در صورتی که متد توسط خود کامپایلر به کلاس اضافه شده باشد، خصوصیت IsSpecialName مقدار true بر میگرداند. بعد از اعمال تغییرات تنها متدهایی که در کلاس Person و کلاس های پدر تعریف شده باشند نمایش داده می شود. متد هایی مانند ToString یا GetHashCode در کلاس Object تعریف شده اند.

در ادامه می خواهیم لیستی از خصوصیات کلاس را در خروجی نمایش دهیم. برای اینکار کد زیر را در ادامه می نویسیم:

var properties = type.GetProperties();
Console.WriteLine();
Console.WriteLine("Properties:");
Console.WriteLine("------------------------");
foreach (PropertyInfo propertyInfo in properties)
{
    if (propertyInfo.GetMethod != null && propertyInfo.SetMethod != null)
    {
        Console.WriteLine("{0} {{ get; set; }}", propertyInfo.Name);
    }
    else if (propertyInfo.GetMethod != null && propertyInfo.SetMethod == null)
    {
        Console.WriteLine("{0} {{ get; }}", propertyInfo.Name);
    }
    else
    {
        Console.WriteLine("{0} {{ set; }}", propertyInfo.Name);
    }
}


بوسیله متد GetProperties لیستی از خصوصیات داخل کلاس را بدست آوردیم. خروجی این متد آرایه ای از نوع PropertyInfo می باشد. این کلاس اطلاعات مربوط به خصوصیت تعریف شده در کلاس را به ما می دهد. در ادامه بوسیله دستور foreach لیست خصوصیات را پیمایش می کنیم. در داخل بدنه foreach، همانطور که مشاهده می کنید، با دستور if بررسی می کنیم که خصوصیات مورد نظر بدنه های get و set را شامل می شود یا خیر. خصوصیت SetMethod اطلاعات مربوط به بخش set و خصوصیت GetMethod اطلاعات مربوط به بخش get را برای ما بر میگرداند. در صورتی عدم وجود هر یک از این بخش ها، خصوصیت مورد نظر مقدار null را بر میگرداند. برای مثال، برای خصوصیت FullName، به دلیل اینکه بخش set نوشته نشده، SetMethod مقدار null را بر خواهد گرداند. خروجی کد بالا به صورت زیر خواهد بود:

Properties:
------------------------
Id { get; set; }
FirstName { get; set; }
LastName { get; set; }
FullName { get; }


در این قسمت از آموزش، با نحوه دریافت برخی از اطلاعات کلاس ها آشنا شدیم و کلاس های زیر را بررسی کردیم:

  1. کلاس Type: شامل اطلاعات مربوط به یک نوع داده که می تواند یک class یا struct یا delegate و نوع های داده پیش فرض دات نت باشد.
  2. متد GetConstrcutors: دریافت لیست سازنده های تعریف شده در یک Type
  3. کلاس ConstructorInfo: شامل اطلاعات مربوط به یک سازنده
  4. متد GetParameters: دریافت لیست پارامترهای تعریف شده در یک سازنده یا یک متد
  5. کلاس ParameterInfo: شامل اطلاعات مربوط به یک پارامتر
  6. متد GetMethods: دریافت لیست متدهای یک Type
  7. کلاس MethodInfo: شامل اطلاعات مربوط به یک متد
  8. متد GetProperties: دریافت لیست متدهای تعریف شده در یک Type
  9. کلاس PropertyInfo: شامل اطلاعات مربوط به یک Property
  10. خصوصیات GetMethod و SetMethod: این خصوصیات در کلاس PropertyInfo تعریف شده و اطلاعات مربوط به بخش های get و set یک property را به ما می دهد


کلاس های دیگری نیز در فضای نام System.Reflection وجود دارند مانند:

  1. EventInfo: اطلاعات مربوط به یک Event
  2. FieldInfo: اطلاعات مربوط به یک Field
  3. MemberInfo: کلاس پایه ای که کلیه کلاس های ParameterInfo، FieldInfo، ConstructorInfo و MethodInfo از آن مشتق شده اند


همچنین متدهای دیگری برای کلاس Type وجود دارند:

  1. متد GetEvents: دریافت لیست Event های تعریف شده در یک Type
  2. متد GetFields: دریافت لیست Field های تعریف شده در یک Type
  3. متد GetInterfaces: دریافت لیست Interface هایی که Type مورد نظر پیاده سازی کرده است


و متدهای دیگر که می توانید از بخش Documentation مایکروسافت به توضیحات آن ها دسترسی داشته باشید.

Late Binding

در دو قسمت قبلی آموزش با مبحث Reflection و نحوه بدست آوردن اطلاعات مربوط به Type ها در زبان سی شارپ آشنا شدیم. در این قسمت از آموزش به مبحث Late Binding می پردازیم. بعد از به دست آوردن اطلاعات Type ها امکان ایجاد شئ و استفاده از اعضاء آن امکان پذیر است. یعنی ما می توانیم بوسیله استفاده از یکسری قابلیت ها و دستورات شئ ها را ایجاد کرده، متدها را فراخوانی کنیم یا خصوصیات را مقدار دهی کنیم. در ابتدا باید با کلاسی به نام Activator که در فضای نام System تعریف شده آشنا شویم. این کلاس به ما قابلیت ایجاد اشیاء در زمان اجرا را می دهد. کلاسی که در قسمت قبلی تعریف کردیم را مجدد یادآوری می کنیم:

public class Person
{
    public Person()
    {
    }

    public Person(int id, string firstName, string lastName)
    {
        Id = id;
        FirstName = firstName;
        LastName = lastName;
    }

    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string FullName
    {
        get { return string.Format("{0} {1}", FirstName, LastName); }
    }

    public void DisplayFullName()
    {
        Console.WriteLine("{0} {1}", FirstName, LastName);
    }
}


با استفاده از کلاس Activator و متد CreateInstance که یک متد static در این کلاس است، می توانیم بعد از به دست آوردن Type کلاس، اقدام با ساخت شئ کنیم. به کد زیر توجه کنید:

Type type = typeof (Person);
object person = Activator.CreateInstance(type);


همانطور که مشاهده می کنید Type کلاس Person به عنوان پارامتر به متد CreateInstance پاس داده شده است. بعد از اجرای کد بالا، متغیر person حاوی یک شئ از نوع Person است. اما نوع بازگشتی متد CreateInstance از نوع object است و می بایست بعد از ساخت شئ آن را به نوع Person تبدیل کنیم. کد بالا را به صورت زیر تغییر می دهیم:

Type type = typeof (Person);
Person person = (Person)Activator.CreateInstance(type);


فراخوانی متد CreateInstance به صورت بالا، باعث فراخوانی سازنده پیش فرض (Default Constructor) کلاس Person می شود. می توان بعد از پارامتری که Type را مشخص می کند، لیست پارامترهایی که برای ایجاد شئ از روی یک سازنده دیگر مورد نیاز است را مشخص کرد. برای اینکار می بایست حتماً Constructor مورد نظر تعریف شده باشد. برای کلاس Person سازنده ای با سه پارامتر با نوع های int و string و string تعریف شده که می توان به صورت زیر، زمان فراخوانی متد CreateInstance پارامترهای مورد نظر را به آن پاس داد:

Type type = typeof (Person);
Person person = (Person) Activator.CreateInstance(type, 12, "Hossein", "Ahmadi");


در اینجا ما برای ساخت شئ نیاز به مشخص کردن Type و تبدیل شئ ایجاد شده به نوع مورد نظر را داریم. یک حالت دیگر از متد CreateInstance وجود دارد که از نوع جنریک است و بر اساس پارامتر جنریک اقدام به ساخت شئ می کند:

Person person = Activator.CreateInstance();


اما مشکلی که وجود دارد، نمی توان پارامتر های مورد نظر برای سازنده را به آن پاس داد. برای رفع این مشکل می توانیم خودمان کلاسی برای ساخت اشئاء به بر اساس پارامتر generic ایجاد کنیم:

public class AdvanceActivator
{
    public static T CreateInstance(params object[] parameters)
    {
        return (T)System.Activator.CreateInstance(typeof (T), parameters);
    }     
}


در کد بالا دقت کنید، کلمه کلیدی typeof بر روی پارامتر جنریک T استفاده شده است، یعنی زمان استفاده از متد CreateInstance، وقتی ما برای مثال نوع Person را برای T مشخص می کنیم، دستور typeof در حقیقت Type کلاس Person را برای ما بر میگرداند و ادامه ماجرا نیز که مانند مثال اولی است که در این بخش توضیح دادیم. استفاده از کلاس بالا نیز به صورت زیر خواهد بود:

Person person = AdvanceActivator.CreateInstance(10, "Hossein", "Ahmadi");


اما گفتیم بحث Late Binding تنها قابلیت ایجاد شئ نیست و می توانیم نسبت به مقدار دهی خصوصیات یا فراخوانی متد ها اقدام کنیم. برای مثال، فرض کنید با دستور زیر شئ ای از روی Person ساختیم، اما عملیات تبدیل object به Person را انجام ندادیم. مطمئناً دسترسی به خصوصیات Person نیز نداریم. با مکانیز زیر می توانیم خصوصیات کلاس ها را مقدار دهی کنیم:

var personType = typeof (Person);

var personInstance = Activator.CreateInstance(personType);

PropertyInfo firstNameProperty = personType.GetProperty("FirstName");

firstNameProperty.SetValue(personInstance, "Hossein");

Console.WriteLine(firstNameProperty.GetValue(personInstance));  


در ابتدا Type کلاس Person را بدست می آوریم. بعد با متد CreateInstance یک شئ از روی Person می سازیم. در قدم بعدی با دستور GetProperty اطلاعات مربوط به خصوصیت FirstName را استخراج می کنیم. کلاس PropertyInfo که در بخش قبلی با آن آشنا شدیم دو متد دارد با نام های SetValue که برای مقدار دهی استفاده می شود و GetValue که برای گرفتن مقدار خصوصیت استفاده می شود. متد SetValue دو پارامتر ورودی دریافت می کند، اولی شئ ای از نوع Person که قرار است عملیات مقدار دهی بر روی آن انجام شود و دوم مقداری که قرار است در خصوصیت قرار بگیرد. اما متد GetValue تنها یک پارامتر می گیرد، شئ ای که قرار است مقدار خصوصیت از آن استخراج شود. مقدار بازگشتی متد GetValue از نوع Object است که می توان در صورت نیاز آن را به نوع داده مورد نظر Cast کرد.

علاوه بر مقدار دهی خصوصیات، می توان عملیات فراخوانی متدها را نیز بوسیله Reflection انجام داد. برای مثال، متد DisplayFullName را کلاس Person به صورت زیر می توانیم فراخوانی کنیم:

var personType = typeof (Person);

var personInstance = Activator.CreateInstance(personType, 10, "Hossein", "Ahmadi");

var method = personType.GetMethod("DisplayFullName");
method.Invoke(personInstance, null);


در کد بالا، با استفاده از متد GetMethod اطلاعات مربوط به متد مورد نظر را بدست آورده و بوسیله متد Invoke که در کلاس MethodInfo تعریف شده است، می توان نسبت به فراخوانی متد مورد نظر اقدام کرد. دقت کنید، متد Invoke دو پارامتر به عنوان ورودی می گیرد، پارامتر اول شئ ای که قرار است عملیات فراخوانی بر روی آن انجام شده و پارامتر دوم آرایه ای از object که پارامتر های متد را مشخص می کند. در مثال بالا، به دلیل اینکه متد DisplayFullName هیچ پارامتری ندارد، مقدار null را به عنوان لیست پارامتر ها پاس دادیم.

Attibute ها

در ادامه موضوع Reflection، به مبحث برنامه نویسی با Attribute ها می پردازیم. به خاطر دارید که در قسمت اول این آموزش گفتیم هر Type ای در دات نت شامل یکسری Meta Data است که آن Type را توصیف می کنند. برای مثال، Meta Data های یک متد نوع بازگشتی آن، نام متد و پارامترهای آن را مشخص می کنند، همچنین Type ای که آن متد در آن تعریف شده و CLR بوسیله این Meta Data ها می تواند از آن ها استفاده کند. همچنین در Visual Studio با پنجره ای به نام Intellisense آشنا شدیم، اطلاعات داخل این پنجره نیز به وسیله Meta Data ها نمایش داده می شوند. اما Attribute ها این بین چه نقشی دارند؟ بوسیله Attribute ها می توانیم یکسری اطلاعات یا Meta Data های اضافی به Type ها و اعضاء Type ها اضافه کنیم. با یک مثال جلو می رویم. کلاسی تعریف می کنیم که شامل یک متد static برای ست کردن تاریخ و زمان است:

public class Utitlities
{
    public static void SetDateAndTime(int year, int month, int day, int hour, int minute, int second)
    {
            
    }
}


بعد از مدتی، تغییری در این کلاس ایجاد می شود، دو متد static جدید با نام های SetTime و SetDate نوشته می شوند که کار ست کردن تاریخ و زمان را به تفکیک انجام می دهند:

public class Utitlities
{
    public static void SetDateAndTime(int year, int month, int day, int hour, int minute, int second)
    {
    }

    public static void SetDate(int year, int month, int day)
    {
            
    }

    public static void SetTime(int hour, int minute, int second)
    {
            
    }
}


خوب تا اینجا مشکلی وجود نداره. حال فرض کنید قصد داریم این کلاس را به برنامه نویسان دیگر نیز بدهیم که از آن استفاده کنند، اما برنامه داریم که برای نسخه بعدی این کلاس، متد SetDateAndTime رو حذف کنیم و باید سایر افراد از این موضوع مطلع باشند. برای اینکار می توان از Attribute ای با نام Obsolete استفاده کرد. از این Attribute به صورت زیر استفاده می کنیم:

[Obsolete]
public static void SetDateAndTime(int year, int month, int day, int hour, int minute, int second)
{
}


همانطور که مشاهده می کنید، نام Attribute را بین [] نوشتیم. با این کار زمان استفاده از کلاس Utilities، در کنار نام متد SetDateAndTime عبارت Deprecated نمایش داده می شود:

آشنایی با قابلیت Reflection در برنامه های دات نت :: قسمت چهارم - برنامه نویسی مبتنی بر Attribute ها



زمان نمایش Intellisense، با خواندن Meta Data های مربوط به Type ها و اعضاء آن، در صورتی که Obsolete بر روی متد استفاده شده باشد، این عبارت نمایش داده می شود. اما استفاده از Attribute به همین مورد خلاصه نمی شود. شما می توانید با نوشتن Attribute های مورد نیاز خود، کارهای متفاوتی انجام دهید. برای ایجاد Attribute های جدید، ابتدا می بایست با کلاسی با نام Attribute آشنا شویم. این کلاس یک کلاس Base برای کلیه Attribute های تعریف شده در سیستم است و تنها می توان از یک کلاس به عنوان Attribute استفاده کرد که از کلاس Attribute مشتق شده باشند. با یک مثال ساده شروع می کنیم. برنامه Meta Data Viewer را به خاطر دارید؟ می خواهیم اطلاعاتی جدیدی به Meta Data های Type ها با نام Author که منظور نویسنده کلاس است اضافه کنیم. برای اینکار ابتدا باید Attribute مورد نظر را تعریف کنیم:

public class AuthorAttribute : Attribute
{

}


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

[Author]
public class Person
{

}


به دو موضوع باید اینجا دقت کنید، یکی اینکه زمان استفاده از Attribute، نیازی به نوشتن عبارت Attribute در انتهای نام آن نیست، سیستم به صورت خودکار این موضوع را تشخیص می دهد. موضوع دوم هم اینکه اگر کلاس AuthorAttribute از کلاس Attribute مشتق نشود، زمان استفاده از کلاس به صورت بالا، پیغام خطا دریافت خواهیم کرد. حال در قدم بعدی می خواهیم Attribute های استفاده شده بر روی Person را به دست آوریم. برای اینکار باید از Reflection کمک بگیریم:

var personType = typeof(Person);
var author = personType.GetCustomAttribute();
if(author != null)
{
    Console.WriteLine("Type has an author!");
}


بوسیله متد GetCustomAttribute که یک متد جنریک است، می توان Attribute مورد نظر را بدست آورد. کافیست نوع Attribute مورد نظر را به عنوان پارامتر جنریک به این متد پاس دهید. در صورتی که Attribute مشخص شده بر روی Type مورد نظر قرار داده شده باشد، شئ ای از نوع آن Attribute و در غیر اینصورت مقدار null بر گردانده خواهد شد. در مثال بالا، با یک شرط بررسی کردیم که اگر author مقدار داشته باشد، پس نویسنده دارد و پیامی را در خروجی چاپ می کنیم. اما Author به این صورت کاربردی ندارد! ما باید بتوانیم زمان استفاده از آن نام نویسنده را نیز مشخص کنیم. کلاس AuthorAttribute را به صورت زیر تغییر می دهیم:

public class AuthorAttribute : Attribute
{
    public string Name { get; set; }
}


حال زمان استفاده از کلاس می توان به صورت زیر عمل کرد:

[Author(Name ="Hossein Ahmadi")]
public class Person
{

}


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

public class AuthorAttribute : Attribute
{
    public AuthorAttribute(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}


و شیوه استفاده از Attribute:

[Author("Hossein Ahmadi")]
public class Person
{

}


به دلیل استفاده از سازنده، دیگر نیازی به نوشتن نام خصوصیت در زمان استفاده از Attribtue نیست. در ادامه کد اصلی را برای نمایش نام نویسنده به صورت زیر تغییر می دهیم:

var personType = typeof(Person);
var author = personType.GetCustomAttribute();
if(author != null)
{
    Console.WriteLine("Type Author: {0}", author.Name);
}


علاوه بر تعریف Attribute ها، می توان یکسری تنظیمات نیز برای آن ها انجام داد. برای مثال Attribute تعریف شده را بتوان چند بار بر روی یک Type استفاده کرد یا خیر یا Attribute تعریف شده تنها بر روی کلاس ها قابل استفاده باشد یا تنها بر روی خصوصیات. برای اینکار باید از Attribute ای با نام AttributeUsage استفاده کنیم که بر روی Attribute های تعریف شده قرار می گیرند. برای مثال، Attribute ای که نوشتیم را جوری تغییر می دهیم که تنها بر روی کلاس ها قابل استفاده باشد و همچنین نتوان بیش از یک بار آن را بر روی یک کلاس نوشت:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AuthorAttribute : Attribute
{
    public AuthorAttribute(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}


پارامتر اول که به صورت سازنده پاس داده می شود از نوع Enum ای با نام AttributeTargets است که در اینجا ما Class را انتخاب کردیم، یعنی Attribute ما تنها بر روی کلاس ها قابل استفاده است، همچنین یک خصوصیت با نام AllowMultiple را به مقدار false ست کردیم. یعنی نمی توان چندین بار این Attribute را بر روی یک کلاس نوشت. Attribute ها کاربردهای فراوانی دارند که یکی از آن ها نوشتن برنامه Extendable است که در بخشی مجزا به این موضوع خواهیم پرداخت. امیدوارم که این آموزش مورد توجه شما قرار گرفته باشد.


حسین احمدی
حسین احمدی

بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب

حسین احمدی ، بنیانگذار TOSINSO ، توسعه دهنده وب و برنامه نویس ، بیش از 12 سال سابقه فعالیت حرفه ای در سطح کلان ، مشاور ، مدیر پروژه و مدرس نهادهای مالی و اعتباری ، تخصص در پلتفرم دات نت و زبان سی شارپ ، طراحی و توسعه وب ، امنیت نرم افزار ، تحلیل سیستم های اطلاعاتی و داده کاوی ...

نظرات