درس شانزدهم – استفاده از صفتها در C#
در این درس با نحوه استفاده از صفتها در زبان C# آشنا خواهید شد. اهداف ما در این درس به شرح زیر است :
1- صفتها چه هستند و چرا از آنها استفاده میکنیم
2- استفاده از صفتهای تک پارامتری و چند پارامتری
3- انواع پارامترهای صفت (پارامترهای Named و Positional)
4- Target های صفتها (عناصری که صفتها بر روی آنها اعمال میشوند)
5- تولید صفتهای شخصی
6- تعریف و یا کنترل موارد استفاده از یک صفت
7- استفاده از پارامترهای Positional و Named در صفتهای شخصی
8- انواع (type) معتبر برای پارامترهای صفت
9- استفاده از صفتها در زمان اجرا
10- خلاصه مطالب
11- منابع
صفتها در حقیقت اطلاعات توضیحی هستند که میتوانید آنها را به برنامههای خود بیفزایید. صفتها را میتوان برای کلیه عناصر برنامه از قبیل کلاسها، واسطها، اسمبلی ها و ... مورد استفاده قرار داد. از این اطلاعات میتوان برای موارد متنوعی در زمان اجرای برنامه استفاده نمود. برای مثال میتوان به صفتی مانند DllImportAttribute اشاره کرد که امکان برقراری ارتباط با توابع کتابخانهای Win32 را فراهم مینماید. همچنین صفتهایی نیز وجود دارند که برنامهنویس یا توسعه دهنده برنامه را در امر تولید برنامه یاری مینمایند. برای مثال میتوان به صفت ObsoleteAttribute اشاره کرد که با استفاده از آن، در زمان کامپایل برنامه پیغامی برای برنامه نویس نمایش داده میشود و مشخص میکند که متدی خاص مورد استفاده قرار نگرفته و یا دیگر مورد استفاده نیست. همچنین هنگامیکه با فرمهای ویندوز کار میکنیم، صفتهای بسیاری وجود دارند که امکان استفاده از این فرمها را فراهم کرده و باعث میشوند تا اطلاعات مربوط به این عناصر در property فرم ظاهر شوند. یکی دیگر از موارد استفاده از صفتها در مسایل امنیتی اسمبلیهای .Net است. برای مثال صفتهایی وجود دارند که باعث جلوگیری از فراخوانیهای غیر مجاز میشوند، بدین معنی که تنها اجازه فراخوانی را به متدها یا اشیایی میدهند که قبلا تعریف شده و مشخص شده باشند.
یکی از علتهای استفاده از صفتها آنست که، اغلب سرویسهایی را که آنها برای کاربر فراهم مینمایند، بسیار پیچیده است و با کدهای معمولی نمیتوان آنرا را بدست آورد. از اینرو استفاده از صفتها در بسیاری از موارد ضروری و اجتناب ناپذیر است. همانطور که خواهید دید، صفتها به برنامههای ما Metadata اضافه مینمایند. پس از کامپایل برنامههای C#، فایل اسمبلی برای آن ایجاد میگردد که این اسمبلی معمولا یا یک فایل اجرایی است و یا یک Dll است. توصیف اسمبلی، در Metadata ی مربوط به آن قرار میگیرد. طی پروسهای تحت عنوان Reflection، صفت یک برنامه از طریق فایل Metadata ی موجود در اسمبلی آن قابل دسترس میگردد. .(برای آشنایی بیشتر با اسمبلی و Metadata میتوانید به " کامپایل یک برنامه سی شارپ " در همین سایت مراجعه نمایید.) در حقیقت صفتها، کلاسهایی هستند که میتوانید آنها را با زبان C# تولید کرده و جهت افزودن اطلاعاتی توضیحی به کد خود، از آنها استفاده نمایید. این اطلاعات در زمان اجرای برنامه از طریق Reflection قابل دسترسی هستند.
در این درس با روش استفاده از صفتها و چگونگی ارتباط دادن آنها با عناصر مختلف برنامه آشنا خواهید شد.
مفاهیم اولیه درباره صفتها
صفتها را معمولا قبل از اعلان عنصر مورد نظر در برنامه قرار میدهند. اعلان صفتها بدین صورت است که نام صفت درون دو براکت قرار میگیرد.
[ObsoleteAttribute]
استفاده از کلمه Attribute در اعلان صفت الزامی نیست، از اینرو اعلان زیر با اعلان فوق یکسان است :
[Obsolete]
همچنین صفتها میتوانند دارای پارامتر نیز باشند که با استفاده از آنها خواص بیشتری را در اختیار برنامه قرار میدهند. در مثال 1-16 موارد متنوعی از استفاده صفت ObsoleteAttribute را مشاهده مینمایید.
مثال 1-16 : نحوه استفاده از صفتها
using System;
class BasicAttributeDemo
{
[Obsolete]
public void MyFirstDeprecatedMethod()
{
Console.WriteLine("Called MyFirstDeprecatedMethod().");
}
[ObsoleteAttribute]
public void MySecondDeprecatedMethod()
{
Console.WriteLine("Called MySecondDeprecatedMethod().");
}
[Obsolete("You shouldn't use this method anymore.")]
public void MyThirdDeprecatedMethod()
{
Console.WriteLine("Called MyThirdDeprecatedMethod().");
}
// make the program thread safe for COM
[STAThread]
static void Main(string[] args)
{
BasicAttributeDemo attrDemo = new BasicAttributeDemo();
attrDemo.MyFirstDeprecatedMethod();
attrDemo.MySecondDeprecatedMethod();
attrDemo.MyThirdDeprecatedMethod();
}
}
همانطور که در مثال 1-16 نیز مشاهده میشود، صفت Obsolete در فرمهای مختلف مورد استفاده قرار گرفته است. اولین محلی که از این صفت استفاده شده است، متد MyFirstDeprecatedMethod() و پس از آن در متد MySecondDeprecatedMethod() است. تنها تفاوت استفاده در این دو حالت آنست که در متد دوم صفت با نام کامل یعنی به همراه کلمه Attribute مورد استفاده قرار گرفته است. نتیجه هر دو اعلان یکسان است. همانطور که گفته بودیم، صفتها میتوانند دارای پارامتر نیز باشند :
[Obsolete("You shouldn't use this method anymore.")]
public void MyThirdDeprecatedMethod()
...
این پارامتر، ویژگی خاصی را به صفت میافزاید که آن را با دو اعلان قبلی متمایز مینماید. نتیجه هر سه اعلان این صفت در زیر آورده شده است. این پیغامها، پیامهای کامپایلر C# هستند که به هنگام کامپایل برنامه تولید شدهاند.
>csc BasicAttributeDemo.cs
Microsoft (R) Visual C# .NET Compiler version 7.10.2292.4
for Microsoft (R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.
BasicAttributeDemo.cs(29,3): warning CS0612:
'BasicAttributeDemo.MyFirstDeprecatedMethod()' is obsolete
BasicAttributeDemo.cs(30,3): warning CS0612:
'BasicAttributeDemo.MySecondDeprecatedMethod()' is obsolete
BasicAttributeDemo.cs(31,3): warning CS0618:
'BasicAttributeDemo.MyThirdDeprecatedMethod()' is obsolete: 'You shouldn't use this method anymore.'
همانطور که ملاحظه میکنید، سومین اعلان صفت در این برنامه که با پارامتر همراه بود، باعث شده است تا پارامتر صفت نیز به عنوان بخشی از پیام نمایش داده شده توسط کامپایلر، نشان داده شود. در مورد دو صفت دیگر نیز مشاهده میشود که تنها پیغامی ساده تولید گردیده است.
مثال 1-16 شامل صفت دیگری نیز میباشد. این صفت STAThreadAttribute است که معمولا در ابتدای کلیه برنامههای C# و قبل از آغاز متد Main() قرار میگیرد. این صفت بیان میدارد که برنامه C# مورد نظر میتواند با کد مدیریت نشده COM از طریق Simple Threading Apartment ارتباط برقرار نماید. استفاده از این صفت در هر برنامهای میتواند مفید باشد، چراکه شما بعنوان برنامه نویس هیچگاه اطلاع ندارید که آیا کنابخانه ثالثی که از آن استفاده میکنید، قصد برقراری ارتباط با COM را دارد یا نه؟ (در صورتیکه با برخی از اصطلاحات بکار رفته آشنایی ندارید اصلا نگران نشوید. در اینجا هدف تنها نشان دادن موارد استفاده از صفتهاست.)
صفتها میتوانند دارای چندین پارامتر باشند. در مثال 2-16، استفاده از دو پارامتر برای یک صفت نشان داده شده است.
مثال 2-16
using System;
public class AnyClass
{
[Obsolete("Don't use Old method, use New method", true)]
static void Old( ) { }
static void New( ) { }
public static void Main( )
{
Old( );
}
}
همانطور که در مثال 2-16 مشاهده میکنید، صفت مورد استفاده دارای دو پارامتر است. پارامتر اول که یک جمله متنی است و همانند مثال 1-16 عمل میکند. پارامتر دوم نیز بیان کننده نوع پیغامی است که این صفت در هنگام کامپایل تولید میکند. در صورتیکه این مقدار برابر با True باشد، بدین معناست که در هنگام کامپایل پیغام خطا تولید میشود و کامپایل برنامه متوقف میگردد. در حالت پیش فرض مقدار این پارامتر برابر با False است که بیان میدارد، به هنگام کامپایل تنها پیغام هشداری تولید خواهد شد. در پیغام این برنامه، عنصری از برنامه را که نباید از آن استفاده شود معین شده و جایگزین آن نیز معرفی میشود.
AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'
نکته مهمی که باید در مورد صفتها در نظر بگیرید آنست که اطلاعاتی که توسط صفت در کد برنامه قرار میگیرد، توسط سایر برنامهها نیز قابل تفسیر و استفاده است.
انواع پارامترهای صفت (پارامترهای Positional و Named)
همانطور که در بالا نیز اشاره شد، صفتها میتوانند دارای پارامتر نیز باشند. این پارامترها به دو دسته تقسیم میشوند. پارامترهای محلی (positional) و پارامترهای اسمی (named). از پارامترهای positional در زمانی استفاده میشود که میخواهیم پارامتر مورد نظر بصورت اجباری مورد استفاده قرار گیرد و البته این مسئله یک قانون نیست ! چراکه در مورد صفت Obsolete، این صفت دارای یک پارامتر positional دیگر با نام error و از نوع int نیز میباشد که ما آنرا در مثال 1-16 لحاظ نکردیم. همانطور که در مثال 2-16 مشاهده کردید، از این پارامتر positional میتوان برای ایجاد یک خطا در زمان کامپایل برنامه استفاده نمود.
[Obsolete("Don't use Old method, use New method", true)]
static void Old( ) { }
تفاوت پارامترهای positional با پارامترهای named در آنست که، پارامترهای named با نامشان مورد استفاده قرار میگیرند و همیشه اختیاری هستند. در مثال 3-16 صفت DllImport را مشاهده مینمایید که دارای هر دو نوع پارامتر positional و named است.
مثال 3-16
using System;
using System.Runtime.InteropServices;
class AttributeParamsDemo
{
[DllImport("User32.dll", EntryPoint="MessageBox")]
static extern int MessageDialog(int hWnd, string msg, string caption, int msgType);
[STAThread]
static void Main(string[] args)
{
MessageDialog(0, "MessageDialog Called!", "DllImport Demo", 0);
}
}
صفت DllImport در مثال 3-16 دارای یک پارامتر positional ("User32.dll") و یک پارامتر named (EntryPoint="MessageBox") است . پارامترهای named در هر مکانی میتوانند قرار گیرند و مانند پارامترهای positional دارای محدودیت مکانی نیستند. بدین معنا که چون در پارامترهای named، نام پارامتر مستقیما مورد استفاده قرار میگیرد، محل قرار گیری آن در لیست پارامترهای صفت مهم نیست اما در مورد پارامترهای positional چون اسم پارامتر مورد استفاده قرار نمیگیرد، این پارامترها حتما باید در مکانهای تعیین شده و تعریف شده در لیست پارامترهای صفت قرار گیرند. توجه کنید که چون هدف ما تنها آشنایی با صفتها و نحوه استفاده از آنهاست، درباره پارامترهای مختلف صفت DllImport بحث نخواهیم کرد چراکه پارامترهای این صفت نیاز به آشنایی کامل با Win32 API دارد.
در یک بررسی کلی میتوان گفت که پارامترهای Positional، پارامترهای سازنده(Constructor) صفت هستند و در هر بار استفاده از صفت باید مورد استفاده قرار گیرند، ولی پارامترهای Named کاملا اختیاری هستند و همیشه نیازی به استفاده از آنها نمیباشد.
Target های صفتها (عناصری که صفتها بر روی آنها اعمال میشوند)
صفتهایی که تا کنون مشاهده کردید، همگی بر روی متدها اعمال شده بودند. اما عناصر مختلف دیگری در C# وجود دارند که میتوان صفتها را بر روی آنها اعمال نمود. جدول 1-16 عناصر مختلف زبان C# را که صفتها بر روی آنها اعمال میشوند را نشان میدهد.
قابل اعمال به .... |
عناصر اعمال شونده |
به تمامی عناصر قابل اعمال هستند. |
all |
به تمام یک اسمبلی |
assembly |
کلاسها |
class |
سازندهها |
constructor |
Delegate ها |
delegates |
عناصر شمارشی |
enum |
رخدادها |
event |
فیلدها |
field |
واسطها |
interface |
متدها |
method |
ماژولها (کدهای کامپایل شدهای که میتوانند به عنوان قسمتی از یک اسمبلی در نظر گرفته شوند.) |
module |
پارامترها |
parameter |
Property ها |
property |
مقادیر بازگشتی |
returnvalue |
ساختارها |
struc |
هر چند ممکن است استفاده از این Target ها باعث ایجاد ابهام شوند، اما میتوان با استفاده از این Target ها معین کرد که صفت دقیقا به عنصر مورد نظر اعمال شود. یکی از صفتهایی که بر روی اسمبلی اعمال میشود و باعث ارتباط با CLS میگردد، صفت CLSCompliantAttribute است. CLS یا همان Common Language Specification امکان برقراری ارتباط بین کلیه زبانهایی که تحت .Net کار میکنند را فراهم مینماید. Target های صفتها با استفاده از اسم Target که بعد از آن کولون قرار میگیرد، ایجاد میشوند. در مثال 4-16 نحوه استفاده از این صفت نشان داده شده است.
مثال 4-16
using System;
[assembly:CLSCompliant(true)]
public class AttributeTargetDemo
{
public void NonClsCompliantMethod(uint nclsParam)
{
Console.WriteLine("Called NonClsCompliantMethod().");
}
[STAThread]
static void Main(string[] args)
{
uint myUint = 0;
AttributeTargetDemo tgtDemo = new AttributeTargetDemo();
tgtDemo.NonClsCompliantMethod(myUint);
}
}
با استفاده از Target مورد نظر در اینجا یعنی assembly، این صفت بر روی کل اسمبلی اعمال میگردد. کد موجود در مثال 4-16 کامپایل نخواهد شد، زیرا uint در متد NonClsCompliantMethod() اعلان شده است. در اینجا درصورتیکه فرم پارامتر صفت CLSCompliant را به false تغییر دهید و یا متد NonClsCompliantMethod() را به متدی منطبق با CLS تبدیل کنید (مثلا نوع بازگشتی آنرا int تعریف کنید) آنگاه برنامه کامپایل خواهد شد. (توضیحی که درباره CLS میتوانم بیان کنم اینست که CLS مجموعهای از ویژگیها و خواص .Net Framework است که به نحوی بیان میدارد، برای اینکه زبانهای مختلف تحت .Net بتوانند بدون مشکل با یکدیگر ارتباط برقرار نمایند، لازم است از یک سری از قوانین پیروی کنند، در غیر اینصورت امکان برقراری ارتباط با سایر کدهای نوسته شده تحت زبانهای برنامهسازی دیگر را نخواهند داشت. برای مثال، استفاده از نوع uint به دلیل اینکه در زبانهای مختلف میتواند به صورتهای متفاوتی پیادهسازی شود و یا وجود نداشته باشد، سازگار با CLS نیست و برای اینکه بخواهیم برنامهای منطبق با CLS داشته باشیم نباید از آن استفاده نماییم.)
نکته قابل توجه در مورد مثال 4-16 آنست که در این مثال صفت CLSCompliant به استفاده از یک Target که همان assembly است، مورد استفاده قرار گرفته است و از اینرو تمامی مشخصات این صفت به کلیه اعضای این اسمبلی اعمال خواهند شد. توجه نمایید که در این مثال علت و موارد استفاده از صفتها مشهودتر است، چراکه همانطور که مشاهده مینمایید، با استفاده از یک صفت میتوانیم کنترلی بر روی کل اسمبلی و برنامه قرار دهیم تا در صورتیکه میخواهیم برنامه ما با سایر زبانهای برنامهسازی تحت .Net ارتباط برقرار کند، از متدهای استاندارد و سازگار با CLS استفاده نماییم که این قابلیت بزرگی را در اختیار ما قرار خواهد داد.
تولید صفتهای شخصی
پس از اینکه با طریقه استفاده از صفتهای موجود در زبان آشنا شدید، حال نوبت به ساخت صفتهای شخصی میرسد. برای تولید یک صفت (Attribute) باید یک کلاس ایجاد نماییم و این کلاس باید از System.Attribute مشتق شود. کلاسی که از System.Attribute مشتق میشود (چه بطور مستقیم و چه بطور غیر مستقیم) یک کلاس صفت(Attribute Class) است. اعلان کلاس صفت باعث ایجاد صفت جدیدی میشود که میتوان از آن در برنامه استفاده نمود. به مثال 5-16 توجه فرمایید.
مثال 5-16
using System;
public class HelpAttribute : Attribute
{
}
در این مثال به سادگی یک صفت جدید تولید کردهایم و میتوانیم از آن استفاده کنیم.
[Help()]
public class AnyClass
{
}
همانطور که قبلا نیز گفتیم استفاده از کلمه Attribute به دنبال نام صفت الزامی نیست. صفتی که در اینجا ایجاد کردهایم عملا کار خاصی برای ما انجام نمیدهد پس اندکی در کد آن تغییر ایجاد میکنیم تا مفیدتر باشد.
مثال 6-16
using System;
public class HelpAttribute : Attribute
{
public HelpAttribute(String Descrition_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
[Help("this is a do-nothing class")]
public class AnyClass
{
}
هماطور که مشاهده میکنید با اضافه کردن چند خط کد توانستیم این صفت را کاراتر کنیم. با قرار دادن یک property در این صفت، پارامتر این صفت بعنوان پیغام نمایش داده میشود.