آموزش #C
مدیران انجمن: athlon64x2, abbas.m.k, شوراي نظارت
- پست: 15889
- تاریخ عضویت: جمعه ۷ بهمن ۱۳۸۴, ۷:۵۱ ب.ظ
- سپاسهای ارسالی: 72671 بار
- سپاسهای دریافتی: 31672 بار
- تماس:
درس سيزدهم – واسطها (Interfaces)
در اين درس با واسطها در زبان C# آشنا خواهيم شد. اهداف اين درس بشرح زير ميباشند :
1- آشنايي با مفهوم كلي واسطها
2- تعريف يك واسط
3- استفاده از يك interface
4- پيادهسازي ارثبري در interface ها
5- نكات مهم و پيشرفته
6- مثالي كاربردي از واسطها
7- منابع مورد استفاده
واسطها از لحاظ ظاهري بسيار شبيه به كلاس هستند با اين تفاوت كه داراي هيچ گونه پيادهسازي نميباشند. تنها چيزي كه در interface به چشم ميخورد تعاريفي نظير رخدادها، متدها، انديكسرها و يا property ها است. يكي از دلايل اينكه واسطها تنها داراي تعاريف هستند و پيادهسازي ندارند آنست كه يك interface ميتوان توسط چندين كلاس يا property مورد ارثبري قرار گيرد، از اينرو هر كلاس يا property خواستار آنست كه خود به پيادهسازي اعضا بپردازد.
حال بايد ديد چرا با توجه به اينكه interface ها داراي پيادهسازي نيستند مورد استفاده قرار ميگيرند يا بهتر بگوئيم سودمندي استفاده از interface ها در چيست؟ تصور كنيد كه در يك برنامه با مولفههايي سروكار داريد كه متغيرند ولي داراي فيلدها يا متدهايي با نامهاي يكساني هستند و بايد نام اين متدها نيز يكسان باشد. با استفاده از يك interface مناسب ميتوان تنها متدها و يا فيلدهاي مورد نظر را اعلان نمود و سپس كلاسها و يا property هاي مورد از آن interface ارثبري نمايند. در اين حالت تمامي كلاسها و property ها داراي فيلدها و يا متدهايي همنام هستند ولي هر يك پيادهسازي خاصي از آنها را اعمال مينمايند.
نكته مهم ديگر درباره interface ها، استفاده و كاربرد آنها در برنامههاي بزرگي است كه برنامهها و يا اشياؤ مختلفي در تماس و تراكنش (transact) هستند. تصور كنيد كلاسي در يك برنامه با كلاسي ديگر در برنامهاي ديگر در ارتباط باشد. فرض كنيد اين كلاس متدي دارد كه مقداري از نوع int بازميگرداند. پس از مدتي طراح برنامه به اين نتيجه ميرسد كه استفاده از int پاسخگوي مشكلش نيست و بايد از long استفاده نمايد. حال شرايط را در نظر بگيريد كه براي تغيير يك چنين مسئله سادهاي چه مشكل بزرگي پيش خواهد آمد. تمامي فيلدهاي مورتبط با اين متد بايد تغيير داده شوند. در ضمن از مسئله side effect نيز نميتوان چشم پوشي كرد.( تاثيرات ناخواسته و غير منتظره و يا به عبارتي پيش بيني نشده كه متغير يا فيلدي بر روي متغير يا فيلدي ديگر اعمال ميكند، در اصطلاح side effect گفته ميشود.) حال فرض كنيد كه در ابتدا interface اي طراحي شده بود. درصورت اعمال جزئيترين تغيير در برنامه مشكل تبديل int به long قابل حل بود، چراكه كاربر يا برنامه و در كل user برنامه در هنگام استفاده از يك interface با پيادهسازي پشت پرده آن كاري ندارد و يا بهتر بگوئيم امكان دسترسي به آن را ندارد. از اينرو اعمال تغييرات درون آن تاثيري بر رفتار كاربر نخواهد داشت و حتي كاربر از آن مطلع نيز نميشود. در مفاهيم كلي شيء گرايي، interface ها يكي از مهمترين و كاربردي ترين اجزاء هستند كه در صورت درك صحيح بسيار مفيد واقع ميشوند. يكي از مثالهاي مشهود درباره interface ها (البته در سطحي پيشرفته تر و بالاتر) رابطهاي كاربر گرافيكي (GUI) هستند. كاربر تنها با اين رابط سروكار دارد و كاري به نحوه عمليات پشت پرده آن ندارد و اعمال تغييرات در پيادهسازي interface كاربر را تحت تاثير قرار نميدهد.
از ديدگاه تكنيكي، واسطها بسط مفهومي هستند كه از آن به عنوان انتزاع (Abstract) ياد ميكنيم. در كلاسهاي انتزاعي (كه با كلمه كليد abstract مشخص ميشدند.) سازندة كلاس قدر بود تا فرم كلاس خود را مشخص نمايد : نام متدها، نوع بازگشتي آنها و تعداد و نوع پارامتر آنها، اما بدون پيادهسازي بدنه متد. يك interface همچنين ميتواند داراي فيلدهايي باشد كه تمامي آنها static و final هستند. يك interface تنها يك فرم كلي را بدون پيادهسازي به نمايش ميگذارد.
از اين ديدگاه، يك واسط بيان ميدارد كه : " اين فرم كلي است كه تمامي كلاسهايي كه اين واسط را پيادهسازي ميكنند، بايد آنرا داشته باشند." از سوي ديگر كلاسها و اشياء ديگري كه از كلاسي كه از يك واسط مشتق شده استفاده ميكنند، ميدانند كه اين كلاس حتماً تمامي متدها و اعضاي واسط را پيادهسازي ميكند و ميتوانند به راحتي از آن متدها و اعضا استفاده نمايند. پس به طور كلي ميتوانيم بگوئيم كه واسطها بمنظور ايجاد يك پروتكل (protocol) بين كلاسها مورد استفاده قرار ميگيرند. (همچنان كه برخي از زبانهاي برنامهسازي بجاي استفاده از كلمه كليدي interface از protocol استفاده مينمايند.)
به دليل اينكه كلاسها و ساختارهايي كه از interface ها ارثبري ميكنند موظف به پيادهسازي و تعريف آنها هستند، قانون و قاعدهاي در اين باره ايجاد ميگردد. براي مثال اگر كلاس A از واسط IDisposable ارثبري كند، اين ضمانت بوجود ميآيد كه كلاس A داراي متد Dispose() است، كه تنها عضو interface نيز ميباشد. هر كدي كه ميخواهد از كلاس A استفاده كند، ابتدا چك مينمايد كه آيا كلاس A واسط IDisposable را پيادهسازي نموده يا خير. اگر پاسخ مثبت باشد آنگاه كد متوجه ميشود كه ميتواند از متد A.Dispose() نيز استفاده نمايد. در زير نحوه اعلان يك واسط نمايش داده شده است.
interface IMyInterface
{
void MethodToImplement();
}
در اين مثال نحوه اعلان واسطي با نام IMyInterface نشان داده شده است. يك قاعده (نه قانون!) براي نامگذاري واسطها آنست كه نام واسطها را با "I" آغاز كنيم كه اختصار كلمه interface است. در interface اين مثال تنها يك متد وجود دارد. اين متد ميتوان هر متدي با انواع مختلف پارامترها و نوع بازگشتي باشد. توجه نماييد همانطور كه گفته شد اين متد داراي پيادهسازي نيست و تنها اعلان شده است. نكته ديگر كه بايد به ان توجه كنيد آنست كه اين متد به جاي داشتن {} به عنوان بلوك خود، داراي ; در انتهاي اعلان خود ميباشد. علت اين امر آنست كه interface تنها نوع بازگشتي و پارامترهاي متد را مشخص مينمايد و كلاس يا شياي كه از آن ارث ميبرد بايد آنرا پيادهسازي نمايد. مثال زير نحوه استفاده از اين واسط را نشان ميدهد.
مثال 1-13 : استفاده از واسطها و ارثبري از آنها
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
}
در اين مثال، كلاس InterfaceImplementer همانند ارثبري از يك كلاس، از واسط IMyInterface ارثبري كرده است. حال كه اين كلاس از واسط مورد نظر ارثبري كرده است، بايد، توجه نماييد بايد، تمامي اعضاي آنرا پيادهسازي كند. در اين مثال اين عمل با پيادهسازي تنها عضو واسط يعني متد MethodToImplement() انجام گرفته است. توجه نماييد كه پيادهسازي متد بايد دقيقا از لحاظ نوع بازگشتي و تعداد و نوع پارامترها شبيه به اعلان موجود در واسط باشد، كوچكترين تغييري باعث ايجاد خطاي كامپايلر ميشود. مثال زير نحوه ارثبري واسطها از يكديگر نيز نمايش داده شده است.
مثال 2-13 : ارثبري واسطها از يكديگر
using System;
interface IParentInterface
{
void ParentInterfaceMethod();
}
interface IMyInterface : IParentInterface
{
void MethodToImplement();
}
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
iImp.ParentInterfaceMethod();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
public void ParentInterfaceMethod()
{
Console.WriteLine("ParentInterfaceMethod() called.");
}
}
مثال 2-13 داراي 2 واسط است : يكي IMyInterface و واسطي كه از آن ارث ميبرد يعني IParentInterface. هنگاميكه واسطي از واسط ديگري ارثبري ميكند، كلاس يا ساختاري كه اين واسطها را پيادهسازي ميكند، بايد تمامي اعضاي واسطهاي موجود در سلسله مراتب ارثبري را پيادهسازي نمايد. در مثال 2-13، چون كلاس InterfaceImplementer از واسط IMyInterface ارثبري نموده، پس از واسط IParentInterface نيز ارثبري دارد، از اينرو بايد كليه اعضاي اين دو واسط را پيادهسازي نمايد.
چند نكته مهم :
1- با استفاده از كلمه كليد interface در حقيقت يك نوع مرجعي (Reference Type) جديد ايجاد نمودهايد.
2- از لحاظ نوع ارتباطي كه واسطها و كلاسها در ارثبري ايجاد مينمايند بايد به اين نكته اشاره كرد كه، ارثبري از كلاس رابطه "است" يا "بودن" (is-a relation) را ايجاد ميكند (ماشين يك وسيله نقليه است) ولي ارثبري از يك واسط يا interface نوع خاصي از رابطه، تحت عنوان "پيادهسازي" (implement relation) را ايجاد ميكند. ("ميتوان ماشين را با وام بلند مدت خريد" كه در اين جمله ماشين ميتواند خريداري شدن بوسيله وام را پيادهسازي كند.)
3- فرم كلي اعلان interface ها بشكل زير است :
[attributes] [access-modifier] interface interface-name [:base-list]{interface-body}
كه در اعضاي آن بشرح زير مي باشند :
attributes : صفتهاي واسط
access-modifiers : private يا public سطح دسترسي به واسط از قبيل
interface-name : نام واسط
:base-list : ليست واسطهايي كه اين واسط آنها را بسط ميدهد.
Interface-body : بدنه واسط كه در آن اعضاي آن مشخص ميشوند
توجه نماييد كه نميتوان يك واسط را بصورت virtual اعلان نمود.
4- هدف از ايجاد يك interface تعيين توانائيهاييست كه ميخواهيم در يك كلاس وجود داشته باشند.
5- به مثالي در زمينه استفاده از واسطها توجه كنيد :
فرض كنيد ميخواهيد واسطي ايجاد نماييد كه متدها و property هاي لازم براي كلاسي را كه ميخواهد قابليت خواندن و نوشتن از/به يك پايگاه داده يا هر فايلي را داشته باشد، توصيف نمايد. براي اين منظور ميتوانيد از واسط IStorable استفاده نماييد.
در اين واسط دو متد Read() و Write() وجود دارند كه در بدنه واسط تعريف ميشوند ك
interface IStorable
{
void Read( );
void Write(object);
}
حال ميخواهيد كلاسي با عنوان Document ايجاد نماييد كه اين كلاس بايد قابليت خواندن و نوشتن از/به پايگاه داده را داشته باشد، پس ميتوانيد كلاس را از روي واسط IStorable پيادهسازي كنيد.
public class Document : IStorable
{
public void Read( ) {...}
public void Write(object obj) {...}
// ...
}
حال بعنوان طراح برنامه، شما وظيفه داري تا به پيادهسازي اين واسط بپردازيد، بطوريكه كليه نيازهاي شما را برآورده نمايد. نمونهاي از اين پيادهسازي در مثال 3-13 آورده شده است.
مثال 3-13 : پيادهسازي واسط و ارثبري – مثال كاربردي
using System;
// interface اعلان
interface IStorable
{
void Read( );
void Write(object obj);
int Status { get; set; }
}
public class Document : IStorable
{
public Document(string s)
{
Console.WriteLine("Creating document with: {0}", s);
}
public void Read( )
{
Console.WriteLine("Implementing the Read Method for IStorable");
}
public void Write(object o)
{
Console.WriteLine("Implementing the Write Method for IStorable");
}
public int Status
{
get
{
return status;
}
set
{
status = value;
}
}
private int status = 0;
}
public class Tester
{
static void Main( )
{
Document doc = new Document("Test Document");
doc.Status = -1;
doc.Read( );
Console.WriteLine("Document Status: {0}", doc.Status);
IStorable isDoc = (IStorable) doc;
isDoc.Status = 0;
isDoc.Read( );
Console.WriteLine("IStorable Status: {0}", isDoc.Status);
}
}
خروجي برنامه نيز بشكل زير است :
Output:
Creating document with: Test Document
Implementing the Read Method for IStorable
Document Status: -1
Implementing the Read Method for IStorable
IStorable Status: 0
6- در مثال فوق توجه نماييد كه براي متدها واسط IStorable هيچ سطح دسترسي (public,private و ...) در نظر گرفته نشده است. در حقيقت تعيين سطح دسترسي باعث ايجاد خطا ميشود چراكه هدف اصلي از ايجاد يك واسط ايجاد شيء است كه تمامي اعضاي آن براي تمامي كلاسها قابل دسترسي باشند.
7- توجه نماييد كه از روي يك واسط نميتوان نمونهاي جديد ايجاد كرد بلكه بايد كلاسي از آن ارثبري نمايد.
8- كلاسي كه از واسط ارثبري ميكند بايد تمامي متدهاي آنرا دقيقا همان گونه كه در واسط مشخص شده پيادهسازي نمايد. به بيان كلي، كلاسي كه از يك واسط ارث ميبرد، فرم و ساختار كلي خود را از واسط ميگيرد و نحوه رفتار و پيادهسازي آنرا خود انجام ميدهد.
خلاصه :
در اين درس با مفاهيم كلي و اصلي درباره واسطها آشنا شديد. هم اكنون ميدانيد كه واسطها چه هستند و سودمندي استفاده از آنها چيست. همچنين نحوه پيادهسازي واسط و ارثبري از آنرا آموختيد.
مبحث واسطها بسيار گسترده و مهم است و اميد است در بخشهاي آينده در سايت، بتوانم تمامي مطالب را بطور حرفهاي و كامل در اختيار شما قرار دهم.
در اين درس با واسطها در زبان C# آشنا خواهيم شد. اهداف اين درس بشرح زير ميباشند :
1- آشنايي با مفهوم كلي واسطها
2- تعريف يك واسط
3- استفاده از يك interface
4- پيادهسازي ارثبري در interface ها
5- نكات مهم و پيشرفته
6- مثالي كاربردي از واسطها
7- منابع مورد استفاده
واسطها از لحاظ ظاهري بسيار شبيه به كلاس هستند با اين تفاوت كه داراي هيچ گونه پيادهسازي نميباشند. تنها چيزي كه در interface به چشم ميخورد تعاريفي نظير رخدادها، متدها، انديكسرها و يا property ها است. يكي از دلايل اينكه واسطها تنها داراي تعاريف هستند و پيادهسازي ندارند آنست كه يك interface ميتوان توسط چندين كلاس يا property مورد ارثبري قرار گيرد، از اينرو هر كلاس يا property خواستار آنست كه خود به پيادهسازي اعضا بپردازد.
حال بايد ديد چرا با توجه به اينكه interface ها داراي پيادهسازي نيستند مورد استفاده قرار ميگيرند يا بهتر بگوئيم سودمندي استفاده از interface ها در چيست؟ تصور كنيد كه در يك برنامه با مولفههايي سروكار داريد كه متغيرند ولي داراي فيلدها يا متدهايي با نامهاي يكساني هستند و بايد نام اين متدها نيز يكسان باشد. با استفاده از يك interface مناسب ميتوان تنها متدها و يا فيلدهاي مورد نظر را اعلان نمود و سپس كلاسها و يا property هاي مورد از آن interface ارثبري نمايند. در اين حالت تمامي كلاسها و property ها داراي فيلدها و يا متدهايي همنام هستند ولي هر يك پيادهسازي خاصي از آنها را اعمال مينمايند.
نكته مهم ديگر درباره interface ها، استفاده و كاربرد آنها در برنامههاي بزرگي است كه برنامهها و يا اشياؤ مختلفي در تماس و تراكنش (transact) هستند. تصور كنيد كلاسي در يك برنامه با كلاسي ديگر در برنامهاي ديگر در ارتباط باشد. فرض كنيد اين كلاس متدي دارد كه مقداري از نوع int بازميگرداند. پس از مدتي طراح برنامه به اين نتيجه ميرسد كه استفاده از int پاسخگوي مشكلش نيست و بايد از long استفاده نمايد. حال شرايط را در نظر بگيريد كه براي تغيير يك چنين مسئله سادهاي چه مشكل بزرگي پيش خواهد آمد. تمامي فيلدهاي مورتبط با اين متد بايد تغيير داده شوند. در ضمن از مسئله side effect نيز نميتوان چشم پوشي كرد.( تاثيرات ناخواسته و غير منتظره و يا به عبارتي پيش بيني نشده كه متغير يا فيلدي بر روي متغير يا فيلدي ديگر اعمال ميكند، در اصطلاح side effect گفته ميشود.) حال فرض كنيد كه در ابتدا interface اي طراحي شده بود. درصورت اعمال جزئيترين تغيير در برنامه مشكل تبديل int به long قابل حل بود، چراكه كاربر يا برنامه و در كل user برنامه در هنگام استفاده از يك interface با پيادهسازي پشت پرده آن كاري ندارد و يا بهتر بگوئيم امكان دسترسي به آن را ندارد. از اينرو اعمال تغييرات درون آن تاثيري بر رفتار كاربر نخواهد داشت و حتي كاربر از آن مطلع نيز نميشود. در مفاهيم كلي شيء گرايي، interface ها يكي از مهمترين و كاربردي ترين اجزاء هستند كه در صورت درك صحيح بسيار مفيد واقع ميشوند. يكي از مثالهاي مشهود درباره interface ها (البته در سطحي پيشرفته تر و بالاتر) رابطهاي كاربر گرافيكي (GUI) هستند. كاربر تنها با اين رابط سروكار دارد و كاري به نحوه عمليات پشت پرده آن ندارد و اعمال تغييرات در پيادهسازي interface كاربر را تحت تاثير قرار نميدهد.
از ديدگاه تكنيكي، واسطها بسط مفهومي هستند كه از آن به عنوان انتزاع (Abstract) ياد ميكنيم. در كلاسهاي انتزاعي (كه با كلمه كليد abstract مشخص ميشدند.) سازندة كلاس قدر بود تا فرم كلاس خود را مشخص نمايد : نام متدها، نوع بازگشتي آنها و تعداد و نوع پارامتر آنها، اما بدون پيادهسازي بدنه متد. يك interface همچنين ميتواند داراي فيلدهايي باشد كه تمامي آنها static و final هستند. يك interface تنها يك فرم كلي را بدون پيادهسازي به نمايش ميگذارد.
از اين ديدگاه، يك واسط بيان ميدارد كه : " اين فرم كلي است كه تمامي كلاسهايي كه اين واسط را پيادهسازي ميكنند، بايد آنرا داشته باشند." از سوي ديگر كلاسها و اشياء ديگري كه از كلاسي كه از يك واسط مشتق شده استفاده ميكنند، ميدانند كه اين كلاس حتماً تمامي متدها و اعضاي واسط را پيادهسازي ميكند و ميتوانند به راحتي از آن متدها و اعضا استفاده نمايند. پس به طور كلي ميتوانيم بگوئيم كه واسطها بمنظور ايجاد يك پروتكل (protocol) بين كلاسها مورد استفاده قرار ميگيرند. (همچنان كه برخي از زبانهاي برنامهسازي بجاي استفاده از كلمه كليدي interface از protocol استفاده مينمايند.)
به دليل اينكه كلاسها و ساختارهايي كه از interface ها ارثبري ميكنند موظف به پيادهسازي و تعريف آنها هستند، قانون و قاعدهاي در اين باره ايجاد ميگردد. براي مثال اگر كلاس A از واسط IDisposable ارثبري كند، اين ضمانت بوجود ميآيد كه كلاس A داراي متد Dispose() است، كه تنها عضو interface نيز ميباشد. هر كدي كه ميخواهد از كلاس A استفاده كند، ابتدا چك مينمايد كه آيا كلاس A واسط IDisposable را پيادهسازي نموده يا خير. اگر پاسخ مثبت باشد آنگاه كد متوجه ميشود كه ميتواند از متد A.Dispose() نيز استفاده نمايد. در زير نحوه اعلان يك واسط نمايش داده شده است.
interface IMyInterface
{
void MethodToImplement();
}
در اين مثال نحوه اعلان واسطي با نام IMyInterface نشان داده شده است. يك قاعده (نه قانون!) براي نامگذاري واسطها آنست كه نام واسطها را با "I" آغاز كنيم كه اختصار كلمه interface است. در interface اين مثال تنها يك متد وجود دارد. اين متد ميتوان هر متدي با انواع مختلف پارامترها و نوع بازگشتي باشد. توجه نماييد همانطور كه گفته شد اين متد داراي پيادهسازي نيست و تنها اعلان شده است. نكته ديگر كه بايد به ان توجه كنيد آنست كه اين متد به جاي داشتن {} به عنوان بلوك خود، داراي ; در انتهاي اعلان خود ميباشد. علت اين امر آنست كه interface تنها نوع بازگشتي و پارامترهاي متد را مشخص مينمايد و كلاس يا شياي كه از آن ارث ميبرد بايد آنرا پيادهسازي نمايد. مثال زير نحوه استفاده از اين واسط را نشان ميدهد.
مثال 1-13 : استفاده از واسطها و ارثبري از آنها
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
}
در اين مثال، كلاس InterfaceImplementer همانند ارثبري از يك كلاس، از واسط IMyInterface ارثبري كرده است. حال كه اين كلاس از واسط مورد نظر ارثبري كرده است، بايد، توجه نماييد بايد، تمامي اعضاي آنرا پيادهسازي كند. در اين مثال اين عمل با پيادهسازي تنها عضو واسط يعني متد MethodToImplement() انجام گرفته است. توجه نماييد كه پيادهسازي متد بايد دقيقا از لحاظ نوع بازگشتي و تعداد و نوع پارامترها شبيه به اعلان موجود در واسط باشد، كوچكترين تغييري باعث ايجاد خطاي كامپايلر ميشود. مثال زير نحوه ارثبري واسطها از يكديگر نيز نمايش داده شده است.
مثال 2-13 : ارثبري واسطها از يكديگر
using System;
interface IParentInterface
{
void ParentInterfaceMethod();
}
interface IMyInterface : IParentInterface
{
void MethodToImplement();
}
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
iImp.ParentInterfaceMethod();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
public void ParentInterfaceMethod()
{
Console.WriteLine("ParentInterfaceMethod() called.");
}
}
مثال 2-13 داراي 2 واسط است : يكي IMyInterface و واسطي كه از آن ارث ميبرد يعني IParentInterface. هنگاميكه واسطي از واسط ديگري ارثبري ميكند، كلاس يا ساختاري كه اين واسطها را پيادهسازي ميكند، بايد تمامي اعضاي واسطهاي موجود در سلسله مراتب ارثبري را پيادهسازي نمايد. در مثال 2-13، چون كلاس InterfaceImplementer از واسط IMyInterface ارثبري نموده، پس از واسط IParentInterface نيز ارثبري دارد، از اينرو بايد كليه اعضاي اين دو واسط را پيادهسازي نمايد.
چند نكته مهم :
1- با استفاده از كلمه كليد interface در حقيقت يك نوع مرجعي (Reference Type) جديد ايجاد نمودهايد.
2- از لحاظ نوع ارتباطي كه واسطها و كلاسها در ارثبري ايجاد مينمايند بايد به اين نكته اشاره كرد كه، ارثبري از كلاس رابطه "است" يا "بودن" (is-a relation) را ايجاد ميكند (ماشين يك وسيله نقليه است) ولي ارثبري از يك واسط يا interface نوع خاصي از رابطه، تحت عنوان "پيادهسازي" (implement relation) را ايجاد ميكند. ("ميتوان ماشين را با وام بلند مدت خريد" كه در اين جمله ماشين ميتواند خريداري شدن بوسيله وام را پيادهسازي كند.)
3- فرم كلي اعلان interface ها بشكل زير است :
[attributes] [access-modifier] interface interface-name [:base-list]{interface-body}
كه در اعضاي آن بشرح زير مي باشند :
attributes : صفتهاي واسط
access-modifiers : private يا public سطح دسترسي به واسط از قبيل
interface-name : نام واسط
:base-list : ليست واسطهايي كه اين واسط آنها را بسط ميدهد.
Interface-body : بدنه واسط كه در آن اعضاي آن مشخص ميشوند
توجه نماييد كه نميتوان يك واسط را بصورت virtual اعلان نمود.
4- هدف از ايجاد يك interface تعيين توانائيهاييست كه ميخواهيم در يك كلاس وجود داشته باشند.
5- به مثالي در زمينه استفاده از واسطها توجه كنيد :
فرض كنيد ميخواهيد واسطي ايجاد نماييد كه متدها و property هاي لازم براي كلاسي را كه ميخواهد قابليت خواندن و نوشتن از/به يك پايگاه داده يا هر فايلي را داشته باشد، توصيف نمايد. براي اين منظور ميتوانيد از واسط IStorable استفاده نماييد.
در اين واسط دو متد Read() و Write() وجود دارند كه در بدنه واسط تعريف ميشوند ك
interface IStorable
{
void Read( );
void Write(object);
}
حال ميخواهيد كلاسي با عنوان Document ايجاد نماييد كه اين كلاس بايد قابليت خواندن و نوشتن از/به پايگاه داده را داشته باشد، پس ميتوانيد كلاس را از روي واسط IStorable پيادهسازي كنيد.
public class Document : IStorable
{
public void Read( ) {...}
public void Write(object obj) {...}
// ...
}
حال بعنوان طراح برنامه، شما وظيفه داري تا به پيادهسازي اين واسط بپردازيد، بطوريكه كليه نيازهاي شما را برآورده نمايد. نمونهاي از اين پيادهسازي در مثال 3-13 آورده شده است.
مثال 3-13 : پيادهسازي واسط و ارثبري – مثال كاربردي
using System;
// interface اعلان
interface IStorable
{
void Read( );
void Write(object obj);
int Status { get; set; }
}
public class Document : IStorable
{
public Document(string s)
{
Console.WriteLine("Creating document with: {0}", s);
}
public void Read( )
{
Console.WriteLine("Implementing the Read Method for IStorable");
}
public void Write(object o)
{
Console.WriteLine("Implementing the Write Method for IStorable");
}
public int Status
{
get
{
return status;
}
set
{
status = value;
}
}
private int status = 0;
}
public class Tester
{
static void Main( )
{
Document doc = new Document("Test Document");
doc.Status = -1;
doc.Read( );
Console.WriteLine("Document Status: {0}", doc.Status);
IStorable isDoc = (IStorable) doc;
isDoc.Status = 0;
isDoc.Read( );
Console.WriteLine("IStorable Status: {0}", isDoc.Status);
}
}
خروجي برنامه نيز بشكل زير است :
Output:
Creating document with: Test Document
Implementing the Read Method for IStorable
Document Status: -1
Implementing the Read Method for IStorable
IStorable Status: 0
6- در مثال فوق توجه نماييد كه براي متدها واسط IStorable هيچ سطح دسترسي (public,private و ...) در نظر گرفته نشده است. در حقيقت تعيين سطح دسترسي باعث ايجاد خطا ميشود چراكه هدف اصلي از ايجاد يك واسط ايجاد شيء است كه تمامي اعضاي آن براي تمامي كلاسها قابل دسترسي باشند.
7- توجه نماييد كه از روي يك واسط نميتوان نمونهاي جديد ايجاد كرد بلكه بايد كلاسي از آن ارثبري نمايد.
8- كلاسي كه از واسط ارثبري ميكند بايد تمامي متدهاي آنرا دقيقا همان گونه كه در واسط مشخص شده پيادهسازي نمايد. به بيان كلي، كلاسي كه از يك واسط ارث ميبرد، فرم و ساختار كلي خود را از واسط ميگيرد و نحوه رفتار و پيادهسازي آنرا خود انجام ميدهد.
خلاصه :
در اين درس با مفاهيم كلي و اصلي درباره واسطها آشنا شديد. هم اكنون ميدانيد كه واسطها چه هستند و سودمندي استفاده از آنها چيست. همچنين نحوه پيادهسازي واسط و ارثبري از آنرا آموختيد.
مبحث واسطها بسيار گسترده و مهم است و اميد است در بخشهاي آينده در سايت، بتوانم تمامي مطالب را بطور حرفهاي و كامل در اختيار شما قرار دهم.
زندگي صحنه يکتاي هنرمندي ماست هرکسي نغمه خود خواند و از صحنه رود
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
لطفا سوالات فني را فقط در خود انجمن مطرح بفرماييد، به اين سوالات در PM پاسخ داده نخواهد شد
- پست: 15889
- تاریخ عضویت: جمعه ۷ بهمن ۱۳۸۴, ۷:۵۱ ب.ظ
- سپاسهای ارسالی: 72671 بار
- سپاسهای دریافتی: 31672 بار
- تماس:
درس چهاردهم – رخدادها و delegate ها در C#
نكته مهم قبل از مطالعه اين درس
توجه نماييد، delegate ها و رخدادها بسيار با يكديگر در تعاملاند، از اينرو در برخي موارد، قبل از آموزش و بررسي رخدادها، به ناچار، از آنها نيز استفاده شده و يا به آنها رجوع شده است. رخدادها در قسمت انتهايي اين درس مورد بررسي قرار ميگيرند، از اينرو در صورتيكه در برخي موارد دچار مشكل شديد و يا درك مطلب برايتان دشوار بود، ابتدا كل درس را تا انتها مطالعه نماييد و سپس در بار دوم با ديدي جديد به مطالب و مفاهيم موجود در آن نگاه كنيد. در اغلب كتابهاي آموزشي زبان C# نيز ايندو مفهوم با يكديگر آورده شدهاند ولي درك رخدادها مستلزم درك و فراگيري كامل delegate هاست، از اينرو مطالب مربوط به delegate ها را در ابتدا قرار دادهام.
هدف ما در اين درس به شرح زير است :
مقدمه
درك اينكه يك delegate چيست؟
اعلان و پيادهسازي delegate ها
درك سودمندي delegate ها
حل مسئله بدون استفاده از delegate
حل مسئله با استفاده از delegate
اعلان delegate ها (بخش پيشرفته)
فراخواني delegate ها (بخش پيشرفته)
ايجاد نمونههاي جديد از يك delegate (بخش پيشرفته)
درك اينكه يك رخداد يا يك event چيست؟
اعلان رخدادها
نكات و توضيحات پيشرفته
ثبت شدن در يك رخداد
لغو عضويت در يك رخداد
فراخواني رخدادها
مثالي پيشرفته از استفاده رخدادها در فرمهاي ويندوز
نكات كليدي درباره رخدادها و delegate ها
منابع مورد استفاده
طي درسهاي گذشته، چگونگي ايجاد و پيادسازي انواع مرجعي (Reference Type) را با استفاده از ساختارهاي زبان C#، يعني كلاسها (Class) و واسطها (Interface)، فرا گرفتيد. همچنين فرا گرفتيد كه با استفاده از اين انواع مرجعي، ميتوانيد نمونههاي جديدي از اشياء را ايجاد كرده و نيازهاي توسعه نرمافزار خود را تامين نماييد. همانطور كه تا كنون ديديد، با استفاده از كلاسها قادر به ساخت اشيائي هستيد كه داراي صفات (Attribute) و رفتارهاي (Behavior) خاصي بودند. با استفاده از واسطها، يكسري از صفات و رفتارها را تعريف ميكرديم تا فرم كلي داشته باشيم و تمام اشياء خود به پيادهسازي اين صفا و رفتارها ميپرداختند. در اين درس با يكي ديگر از انواع مرجعي (Reference Type) در زبان C# آشنا خواهيد شد.
مقدمهاي بر رخدادها و delegate ها
در گذشته، پس از اجراي يك برنامه، برنامه مراحل اجراي خود را مرحله به مرحله اجرا مينمود تا به پايان برسد. در صورتيكه نياز به ارتباط و تراكنش با كاربر نيز وجود داشت، اين امر محدود و بسيار كنترل شده صورت ميگرفت و معمولاً ارتباط كاربر با برنامه تنها پر كردن و يا وارد كردن اطلاعات خاصي در فيلدهايي مشخص بود.
امروزه با پبشرفت كامپيوتر و گسترش تكنولوژيهاي برنامه نويسي و با ظهور رابطهاي كاربر گرافيكي (GUI) ارتباط بين كاربر و برنامه بسيار گسترش يافته و ديگر اين ارتباط محدود به پر كردن يكسري فيلد نيست، بلكه انواع عمليات از سوي كاربر قابل انجام است. انتخاب گزينهاي خاص در يك منو، كليك كردن بر روي دكمهها براي انجام عملياتي خاص و ... . رهيافتي كه امروزه در برنامهنويسي مورد استفاده است، تحت عنوان "برنامهنويسي بر پايه رخدادها" (Event-Based Programming) شناخته ميشود. در اين رهيافت برنامه همواره منتظر انجام عملي از سوي كاربر ميماند و پس از انجام عملي خاص، رخداد مربوط به آن را اجرا مينمايد. هر عمل كاربر باعث اجراي رخدادي ميشود. در اين ميان برخي از رخدادها بدون انجام عملي خاص از سوي كاربر اجرا ميشوند، همانند رخدادهاي مربوط به ساعت سيستم كه مرتباً در حال اجرا هستند.
رخدادها (Events) بيان اين مفهوم هستند كه در صورت اتفاق افتادن عملي در برنامه، كاري بايد صورت گيرد. در زبان C# مفاهيم Event و Delegate دو مفهوم بسيار وابسته به يكديگر هستند و با يكديگر در تعامل ميباشند. براي مثال، مواجهه با رخدادها و انجام عمل مورد نظر در هنگام اتفاق افتادن يك رخداد، نياز به يك event handler دارد تا در زمان بروز رخداد، بتوان به آن مراجعه نمود. Event handler ها در C# معمولاً با delegate ها ساخته ميشوند.
از delegate ، ميتوان به عنوان يك Callback ياد نمود، بدين معنا كه يك كلاس ميتواند به كلاسي ديگر بگويد : "اين عمل خاص را انجام بده و هنگاميكه عمليات را انجام دادي منرا نيز مطلع كن". با استفاده از delegate ها، همچنين ميتوان متدهايي تعريف نمود كه تنها در زمان اجرا قابل دسترسي باشند.
Delegate
Delegate ها، يكي ديگر از انواع مرجعي زبان C# هستند كه با استفاده از آنها ميتوانيد مرجعي به يك متد داشته باشيد، بدين معنا كه delegate ها، آدرس متدي خاص را در خود نگه ميدارند. در صورتيكه قبلاً با زبان C برنامهنويسي كردهايد، حتماً با اين مفهوم آشنايي داريد. در زبان C اين مفهوم با اشارهگرها (pointer) بيان ميشود. اما براي افرادي كه با زبانهاي ديگري برنامهنويسي ميكردهاند و با اين مفهوم مانوس نيستند، شايد اين سوال مطرح شود كه چه نيازي به داشتن آدرس يك متد وجود دارد. براي پاسخ به اين سوال اندكي بايد تامل نماييد.
بطور كلي ميتوان گفت كه delegate نوعي است شبيه به متد و همانند آن نيز رفتار ميكند. در حقيقت delegate انتزاعي (Abstraction) از يك متد است. در برنامهنويسي ممكن به شرايطي برخورد كرده باشيد كه در آنها ميخواهيد عمل خاصي را انجام دهيد اما دقيقاً نميدانيد كه بايد چه متد يا شيءاي را براي انجام آن عمل خاص مورد استفاده قرار دهيد. در برنامههاي تحت ويندوز اين گونه مسائل مشهودتر هستند. براي مثال تصور كنيد در برنامه شما، دكمهاي قرار دارد كه پس از فشار دادن اين دكمه توسط كاربر شيءاي يا متدي بايد فراخواني شود تا عمل مورد نظر شما بر روي آن انجام گيرد. ميتوان بجاي اتصال اين دكمه به شيء يا متد خاص، آنرا به يك delegate مرتبط نمود و سپس آن delegate را به متد يا شيء خاصي در هنگام اجراي برنامه متصل نمود.
ابتدا، به نحوه استفاده از متدها توجه نماييد. معمولاً، براي حل مسايل خود الگوريتمهايي طراحي مينائيم كه اين الگوريتمهاي كارهاي خاصي را با استفاده از متدها انجام ميدهد، ابتدا متغيرهايي مقدار دهي شده و سپس متدي جهت پردازش آنها فراخواني ميگردد. حال در نظر بگيريد كه به الگوريتمي نياز داريد كه بسيار قابل انعطاف و قابل استفاده مجدد (reusable) باشد و همچنين در شرايط مختلف قابليتهاي مورد نظر را در اختيار شما قرار دهد. تصور كنيد، به الگوريتمي نياز داريد كه از نوعي از ساختمان داده پشتيباني كند و همچنين ميخواهيد اين ساختمان داده را در مواردي مرتب (sort) نماييد، بعلاوه ميخواهيد تا اين ساختمان داده از انواع مختلفي تشكيل شده باشد. اگر انواع موجود در اين ساختمان داده را ندانيد، چكونه ميخواهيد الگوريتمي جهت مقايسه عناصر آن طراحي كنيد؟ شايد از يك حلقه if/then/else و يا دستور switch براي اين منظور استفاده كنيد، اما استفاده از چنين الگوريتمي محدوديتي براي ما ايجاد خواهد كرد. روش ديگر، استفاده از يك واسط است كه داراي متدي عمومي باشد تا الگوريتم شما بتواند آنرا فراخواني نمايد، اين روش نيز مناسب است، اما چون مبحث ما در اين درس delegate ها هستند، ميخواهيم مسئله را از ديدگاه delegate ها مورد بررسي قرار دهيم. روش حل مسئله با استفاده از آنها اندكي متفاوت است.
روش ديگر حل مسئله آنست كه، ميتوان delegate ي را به الگوريتم مورد نظر ارسال نمود و اجازه داد تا متد موجود در آن،عمل مورد نظر ما را انجام دهد. چنين عملي در مثال 1-14 نشان داده شده است.
(به صورت مسئله توجه نماييد : ميخواهيم مجموعهاي از اشياء را كه در يك ساختمان داده قرار گرفتهاند را مرتب نمائيم. براي اينكار نياز به مقايسه اين اشياء با يكديگر داريم. از آنجائيكه اين اشياء از انواع (type) مختلف هستند به الگوريتمي نياز داريم تا بتواند مقايسه بين اشياء نظير را انجام دهد. با استفاده از روشهاي معمول اين كار امكان پذير نيست، چراكه نميتوان اشيائئ از انواع مختلف را با يكديگر مقايسه كرد. براي مثال شما نميتوانيد نوع عددي int را با نوع رشتهاي string مقايسه نماييد. به همين دليل با استفاده از delegate ها به حل مسئله پرداختهايم. به مثال زير به دقت توجه نماييد تا بتوانيد به درستي مفهوم delegate را درك كنيد.)
مثال 1-14 : اعلان و پيادهسازي يك delegate
using System;
// در اينجا اعلان ميگردد. delegate
public delegate int Comparer(object obj1, object obj2);
public class Name
{
public string FirstName = null;
public string LastName = null;
public Name(string first, string last)
{
FirstName = first;
LastName = last;
}
// delegate method handler
public static int CompareFirstNames(object name1, object name2)
{
string n1 = ((Name)name1).FirstName;
string n2 = ((Name)name2).FirstName;
if (String.Compare(n1, n2) > 0)
{
return 1;
}
else if (String.Compare(n1, n2) < 0)
{
return -1;
}
else
{
return 0;
}
}
public override string ToString()
{
return FirstName + " " + LastName;
}
}
class SimpleDelegate
{
Name[] names = new Name[5];
public SimpleDelegate()
{
names[0] = new Name("Meysam", "Ghazvini");
names[1] = new Name("C#", "Persian");
names[2] = new Name("Csharp", "Persian");
names[3] = new Name("Xname", "Xfamily");
names[4] = new Name("Yname", "Yfamily");
}
static void Main(string[] args)
{
SimpleDelegate sd = new SimpleDelegate();
// delegate ساخت نمونهاي جديد از
Comparer cmp = new Comparer(Name.CompareFirstNames);
Console.WriteLine("\nBefore Sort: \n");
sd.PrintNames();
sd.Sort(cmp);
Console.WriteLine("\nAfter Sort: \n");
sd.PrintNames();
}
public void Sort(Comparer compare)
{
object temp;
for (int i=0; i < names.Length; i++)
{
for (int j=i; j < names.Length; j++)
{
//همانند يك متد استفاده ميشود compare از
if ( compare(names[i], names[j]) > 0 )
{
temp = names[i];
names[i] = names[j];
names[j] = (Name)temp;
}
}
}
}
public void PrintNames()
{
Console.WriteLine("Names: \n");
foreach (Name name in names)
{
Console.WriteLine(name.ToString());
}
}
}
اولين اعلان در اين برنامه، اعلان delegate است. اعلان delegate بسيا رشبيه به اعلان متد است، با اين تفاوت كه داراي كلمه كليدي delegate در اعلان است و در انتهاي اعلان آن ";" قرار ميگيرد و نيز پيادهسازي ندارد. در زير اعلان delegate كه در مثال 1-14 آورده شده را مشاهده مينماييد :
public delegate int Comparer(object obj1, object obj2);
اين اعلان، مدل متدي را كه delegate ميتواند به آن اشاره كند را تعريف مينمايد. متدي كه ميتوان از آن بعنوان delegate handler براي Comparer استفاده نمود، هر متدي ميتواند باشد اما حتماً بايد پارامتر اول و دوم آن از نوع object بوده و مقداري از نوع int بازگرداند. در زير متدي كه بعنوان delegate handler در مثال 1-14 مورد استفاده قرار گرفته است، نشان داده شده است :
public static int ComparerFirstNames(object name1, object name2)
{
…
}
براي استفاده از delegate ميبايست نمونهاي از آن ايجاد كنيد. ايجاد نمونه جديد از delegate همانند ايجاد نمونهاي جديد از يك كلاس است كه به همراه پارامتري جهت تعيين متد delegate handler ايجاد ميشود :
Comparer cmp = new Comparer(Name.ComparerFirstName);
در مثال 1-14، cmp بعنوان پارامتري براي متد Sort() مورد استفاده قرار گرفته است. به روش ارسال delegate به متد Sort() توجه نماييد :
sd.Sort(cmp);
با استفاده از اين تكنيك، هر متد delegate handler به سادگي در زمان اجرا به متد Sort() قابل ارسال است. براي مثال ميتوان handler ديگري با نام CompareLastNames() تعريف كنيد، نمونه جديدي از Comparer را با اين پارامتر ايجاد كرده و سپس آنرا به متد Sort() ارسال نماييد.
درك سودمندي delegate ها
براي درك بهتر delegate ها به بررسي يك مثال ميپردازيم. در اينجا اين مثال را يكبار بدون استفاده از delegate و بار ديگر با استفاده از آن حل كرده و بررسي مينمائيم. مطالب گفته شده در بالا نيز به نحوي مرور خواهند شد. توجه نماييد، همانطور كه گفته شد delegate ها و رخدادها بسيار با يكديگر در تعاملاند، از اينرو در برخي موارد به ناچار از رخدادها نيز استفاده شده است. رخدادها در قسمت انتهايي اين درس آورده شدهاند، از اينرو در صورتيكه در برخي موارد دچار مشكل شديد و يا درك مطلب برايتان دشوار بود، ابتدا كل درس را تا انتها مطالعه نماييد و سپس در بار دوم با ديدي جديد به مطالب و مفاهيم موجود در آن نگاه كنيد. در اغلب كتابهاي آموزشي زبان C# نيز ايندو مفهوم با يكديگر آورده شدهاند ولي درك رخدادها مستلزم درك و فراگيري كامل delegate هاست، از اينرو مطالب مربوط به delegate ها را در ابتدا قرار دادهام.
حل مسئله بدون استفاده از delegate
فرض كنيد، ميخواهيد برنامه بنويسيد كه عمل خاصي را هر يك ثانيه يكبار انجام دهد. يك روش براي انجام چنين عملي آنست كه، كار مورد نظر را در يك متد پيادهسازي نماييد و سپس با استفاده از كلاسي ديگر، اين متد را هر يك ثانيه يكبار فراخواني نمائيم. به مثال زير توجه كنيد :
class Ticker
{
⋮
public void Attach(Subscriber newSubscriber)
{
subscribers.Add(newSubscriber);
}
public void Detach(Subscriber exSubscriber)
{
subscribers.Remove(exSubscriber);
}
// هر ثانيه فراخواني ميگردد Notify
private void Notify()
{
foreach (Subscriber s in subscribers)
{
s.Tick();
}
}
⋮
private ArrayList subscribers = new ArrayList();
}
class Subscriber
{
public void Tick()
{
⋮
}
}
class ExampleUse
{
static void Main()
{
Ticker pulsed = new Ticker();
Subscriber worker = new Subscriber();
pulsed.Attach(worker);
⋮
}
}
اين مثال مطمئناً كار خواهد كرد اما ايدآل و بهينه نيست. اولين مشكل آنست كه كلاس Ticker بشدت وابسته به Subscriber است. به بيان ديگر تنها نمونههاي جديد كلاس Subscriber ميتوانند از كلاس Ticker استفاده نمايند. اگر در برنامه كلاس ديگري داشته باشيد كه بخواهيد آن كلاس نيز هر يك ثانيه يكبار اجرا شود، ميبايست كلاس جديدي شبيه به Ticker ايجاد كنيد. براي بهينه كردن اين مسئله ميتوانيد از يك واسط (Interface) نيز كمك بگيريد. براي اين منظور ميتوان متد Tick را درون واسطي قرار داد و سپس كلاس Ticker را به اين واسط مرتبط نمود.
interface Tickable
{
void Tick();
}
class Ticker
{
public void Attach(Tickable newSubscriber)
{
subscribers.Add(newSubscriber);
}
public void Detach(Tickable exSubscriber)
{
subscribers.Remove(exSubscriber);
}
// هر ثانيه فراخواني ميگردد Notify
private void Notify()
{
foreach (Tickable t in subscribers)
{
t.Tick();
}
}
⋮
private ArrayList subscribers = new ArrayList();
}
اين راه حل اين امكان را براي كليه كلاسها فراهم مينمايد تا واسط Tickable را پيادهسازي كنند.
class Clock : Tickable
{
⋮
public void Tick()
{
⋮
}
⋮
}
class ExampleUse
{
static void Main()
{
Ticker pulsed = new Ticker();
Clock wall = new Clock();
pulsed.Attach(wall);
⋮
}
}
حال به بررسي همين مثال با استفاده از delegate خواهيم پرداخت.
حل مسئله با استفاده از delegate
استفاده از واسطها در برنامهها، مطمئناً روشي بسيار خوب است، اما كامل نبوده اشكالاتي دارد. مشكل اول آنست كه اين روش بسيار كلي و عمومي است. تصور نماييد ميخواهيد از تعداد زيادي از سرويسها استفاده نماييد(بعنوان مثال در برنامههاي مبتني بر GUI. در اينگونه برنامهها هجم عظيمي از رخدادها وجود دارند كه ميبايست با تمامي آنها در ارتباط باشيد.) مشكل ديگر آنست كه استفاده از واسط، بدين معناست كه متد Tick بايد متدي public باشد، از اينرو هر كدي ميتواند Clock.Tick را در هر زماني فراخواني نمايد. روش مناسب تر آنست كه مطمئن شويم تنها اعضايي خاص قادر به فراخواني و دسترسي به Clock.Tick هستند. با استفاده از delegate تمامي اين امكانات براي ما فراهم خواهد شد و برنامههايي با ايمني بالاتر و پايدارتر ميتوانيم داشته باشيم.
اعلان Delegate
در مثال ما، متد Tick از واسط Tickable از نوع void بود و هيچ پارامتري دريافت نميكرد :
interface Tickable
{
void Tick();
}
براي اين متد ميتوان delegate ي تعريف نمود كه ويژگيهاي آنرا داشته باشد :
delegate void Tick();
همانطور كه قبلاً نيز گفته شد، اين عمل نوع جديدي را ايجاد مينمايد كه ميتوان از آن همانند ساير انواع استفاده نمود. مثلاً ميتوان آنرا بعنوان پارامتري براي يك متد در نظر گرفت :
void Example(Tick param)
{
⋮
}
فراخواني delegate
قدرت و توانايي delegate زماني مشهود ميگردد كه ميخواهيد از آن استفاده نماييد. براي مثال، با متغير param در مثال قبل چكار ميتوانيد انجام دهيد؟ اگر param متغيري از نوع int بود، از مقدار آن استفاده ميكرديد و با استفاده از عملگرهايي نظير +، - و يا عملگرهاي مقايسهاي، عملي خاص را بر روي آن انجام ميداديد. اما حال كه param متغيري از نوع int نيست، چه ميكنيد؟ متغير param يك delegate است و همانطور كه گفته شد، delegate انتزاعي از يك متد است، پس هر عملي كه متد انجام ميدهد، delegate نيز ميتواند انجام دهد. با استفاده از پرانتز، ميتوان از delegate استفاده نمود :
void Example(Tick param)
{
param();
}
نكته : همانطور كه اشاره شد، delegate يكي از انواع مرجعي است از اينرو مقدار آن ميتواند برابر با Null باشد. در مثال فوق، اگر مقدار param برابر با Null باشد، كامپايلر خطاي NullReferenceException را ايجاد مينمايد.
همانند متدها، delegate ها بايد بطور كامل و صحيح فراخواني گردند. با توجه به اعلان Tick، در زمان فراخواني اين delegate، مثلاً param، بايد توجه داشت كه هيچ پارامتري را نميتوان به آن ارسال نمود و نميتوان آنرا به متغيري نسبت داد چراكه اين delegate بصورت void اعلان شده و مقدار بازگشتي ندارد.
void Example(Tick param)
{
param(42); // خطاي زمان كامپايل رخ ميدهد
int hhg = param(); // خطاي زمان كامپايل رخ ميدهد
Console.WriteLine(param()); // خطاي زمان كامپايل رخ ميدهد
}
توجه نماييد كه delegate را به هر نحوي ميتوانيد اعلان نماييد. براي مثال به نسخة ديگري از Tick توجه كنيد :
delegate void Tick(int hours, int minutes, int seconds);
اما به ياد داشته باشيد كه همانند متد، در هنگام استفاده از آن بايد پارامترهاي صحيح به آن ارسال نماييد :
void Example(Tick method)
{
method(12, 29, 59);
}
با استفاده از delegate ميتوانيد كلاس Ticker را پيادهسازي كنيد :
delegate void Tick(int hours, int minutes, int seconds);
class Ticker
{
⋮
public void Attach(Tick newSubscriber)
{
subscribers.Add(newSubscriber);
}
public void Detach(Tick exSubscriber)
{
subscribers.Remove(exSubscriber);
}
private void Notify(int hours, int minutes, int seconds)
{
foreach (Tick method in subscribers)
{
method(hours, minutes, seconds);
}
}
⋮
private ArrayList subscribers = new ArrayList();
}
ساخت نمونههاي جديد از يك delegate
آخرين كاري كه بايد انجام دهيد، ايجاد نمونههاي جديد از delegate ساخته شده است. يك نمونة جديد از يك delegate، تنها انتزاعي از يك متد است كه با نامگذاري آن متد ايجاد ميشود.
class Clock
{
⋮
public void RefreshTime(int hours, int minutes, int seconds)
{
⋮
}
⋮
}
با توجه به ساختار Tick، ملاحظه مينماييد كه متد RefreshTime كاملاً با اين delegate همخواني دارد :
delegate void Tick(int hours, int minutes, int seconds);
و اين بدين معناست كه ميتوان نمونة جديد از Tick ايجاد كرد كه انتزاعي از فراخواني RefreshTime در شيء خاصي از Clock است.
Clock wall = new Clock();
⋮
Tick m = new Tick(wall.RefreshTime);
حال كه m، ايجاد شد، ميتوانيد از آن بصورت زير استفاده نماييد :
m(12, 29, 59);
اين دستور در حقيقت كار دستور زير را انجام ميدهد (چون m دقيقاً انتزاع آن است) :
wall.RefreshTime(12, 29, 59);
همچنين ميتوانيد m را بعنوان پارامتر به متدي ارسال نماييد. حال تمام چيزهايي را كه براي حل مسئله با استفاده از delegate بدانها نياز داشتيم را بررسي كرديم. در زير مثالي را مشاهده ميكنيد كه كلاسهاي Ticker و Clock را به يكديگر مرتبط نموده است. در اين مثال از واسط استفاده نشده و متد RefreshTime، متدي private است :
delegate void Tick(int hours, int minutes, int seconds);
⋮
class Clock
{
⋮
public void Start()
{
ticking.Attach(new Tick(this.RefreshTime));
}
public void Stop()
{
ticking.Detach(new Tick(this.RefreshTime));
}
private void RefreshTime(int hours, int minutes, int seconds)
{
Console.WriteLine("{0}:{1}:{2}", hours, minutes, seconds);
}
private Ticker ticking = new Ticker();
}
با اندكي تامل و صرف وقت ميتوانيد delegate را بطور كامل درك نماييد.
رخدادها (Events)
در برنامههاي Console ، برنامه منتظر ورود اطلاعات يا دستوراتي از سوي كاربر ميماند و با استفاده از اين اطلاعات كار مورد نظر را انجام ميدهند. اين روش برقراري ارتباط با كاربر، روشي ناپايدار و غير قابل انعطاف است. در مقابل برنامههاي Console، برنامههاي مدرن وجود دارند كه با استفاده از GUI با كاربر در ارتباطند و بر پايه رخدادها بنا شدهاند (Event-Based)، بدين معنا كه رخدادي (منظور از رخداد اتفاقي است كه در سيستم يا محيط برنامه صورت ميگيرد.) در سيستم روي ميدهد و بر اساس اين رخداد عملي در سيستم انجام ميشود. در برنامههاي تحت ويندوز، نيازي به استفاده از حلقههاي متعدد جهت منتظر ماندن براي ورودي از كاربر نيست، بلكه با استفاده از رخدادها، تراكنش بين سيستم و كاربر كنترل ميشود.
يك event در زبان C#، عضوي از كلاس است، كه در صورت بروز رخداد خاصي، فعال ميشود و عملي را انجام ميدهد. معمولاً براي فعال شده event از دو عبارت fires و raised استفاده ميشود. هر متدي كه بخواهد، ميتواند در ليست رخداد ثبت شده و به محض اتفاق افتادن آن رخداد، از آن مطلع گردد.
بطور كلي ميتوان گفت كه يك رخداد همانند يك فيلد اعلان ميشود با اين تفاوت مهم كه نوع آنها حتماٌ بايد يك delegate باشد.
Delegate و رخدادها در كنار يكديگر كار ميكنند تا قابليتهاي يك برنامه را افزايش دهند. اين پروسه با شروع يك كلاس كه يك رخداد را تعريف ميكند، آغاز ميشود. هر كلاسي، كه اين رخداد را درون خود داشته باشد، در آن رخداد ثبت شده است و ميتواند متدي را به آن رخداد تخصيص دهد. اين عمل با استفاده از delegate ها صورت ميپذيرد، بدين معني كه delegate متدي را كه براي رخداد ثبت ميشود را تعيين مينمايد. Delegate ها ميتوانند هر يك از delegate هاي از پيش تعريف شدة .Net و يا هر delegate ي باشند كه توسط كاربر تعريف شده است. بطور كلي، delegate ي را به رخدادي تخصيص ميدهيم تا متدي را كه بهنگام روي دادن رخداد فراخواني ميشود، معين گردد. مثال زير روش تعريف رخداد را نشان ميدهد.
مثال 2-14 : اعلان و پيادهسازي رخدادها
using System;
public delegate void MyDelegate();
class Listing14-2
{
public static event MyDelegate MyEvent;
static void Main()
{
MyEvent += new MyDelegate(CallbackMethod);
// فراخواني رخداد
MyEvent();
Console.ReadLine();
}
public static void CallbackMethod()
{
Console.WriteLine("CallbackMethod.");
}
}
در اين مثال، ابتدا اعلان يك delegate ديده ميشود. درون كلاس، رخدادي با نام MyEvent و از نوع MyDelegate تعريف شده است. در متد Main() نيز مرجع جديدي به رخداد MyEvent افزوده شده است. همانطور كه در اين مثال نيز مشاهده ميكنيد، delegate ها تنها با استفاده از += ميتوانند به رخدادها افزوده شوند. در اين مثال هر گاه MyEvent فراخواني شود، متد CallbackMethod اجرا ميشود چراكه با استفاده از مرجع delegate به رخداد مرتبط شده است. (يا در اصطلاح در رخداد ثبت شده است.)
مثال فوق را بدون استفاده از رخداد نيز ميتوان نوشت. اين نسخه از مثال 2-14 كه تنها در آن از delegate استفاده شده در زير آورده شده است :
using System;
public delegate void MyDelegate();
class UsingDelegates
{
static void Main()
{
MyDelegate del = new MyDelegate(CallbackMethod);
// delegate فراخواني
del();
Console.ReadLine();
}
public static void CallbackMethod()
{
Console.WriteLine("CallbackMethod.");
}
}
بايد توجه كنيد كه موارد كاربرد رخدادها بيشتر در برنامههاي تحت ويندوز نمايان ميشود و در اينجا شايد وجود آنها در برنامه براي شما مشهود نباشد. در آينده، به بررسي برنامهنويسي فرمهاي ويندوز نيز خواهيم رسيد و در آنجا به طور مفصل درباره event ها و delegate ها مجدداً بحث خواهيم نمود.
بطور خلاصه ميتوان گفت، با استفاده از delegate ها روشي براي ايجاد دسترسي به متدها بط.ور پويا را فراهم نموديم. با استفاده از رخدادها نيز، در صورت بروز اتفاقي خاص، عملي خاص انجام ميگيرد. اين عمل معمولاٌ با استفاده از يك delegate كه مرجعي به يك متد در خود دارد انجام ميگيرد.
توضيحات پيشرفته :
در انتهاي اين درس ميخواهم توضيحات پيشرفته تري را نيز در اختيار شما قرار دهم. در قسمت مربوط به delegate ها در همين درس، مثالي مطرح شد كه در آن delegate ي با نام Tick وجود داشت. اعلان اين delegate به صورت زير بود :
delegate void Tick(int hours, int minutes, int seconds);
حال ميخواهيم به اين مثال يك رخداد نيز اضافه كنيم. در زير رخداد tick از نوع Tick اعلان شده است :
class Ticker
{
public event Tick tick;
⋮
}
بايد توجه نماييد كه يك رخداد بطور خودكار ليست اعضاي خود را مديريت ميكند و نيازي به استفاده از يك مجموعه، مانند آرايه، براي مديريت اعضاي مرتبط با آن نيست.
نكته : يك رخداد بطور خودكار خود را تخصيص دهي ميكند و نيازي به ساخت نمونة جديد از روي يك رخداد وجود ندارد.
عضو شدن در يك رخداد (تبث شدن در يك رخداد)
براي افزودن delegate جديد به يك رخداد كافيست تا از عملگر += استفاده نماييم. مثال زير كلاس Clock را نشان ميدهد كه در آن فيلدي از نوع Ticker با نام pulsed وجود دارد. كلاس Ticker داراي رخداد tick از نوع delegate ي بنام Tick است. متد Clock.Start، delegate ي از نوع Tick را با استفاده از عملگر += به pulsed.tick ميافزايد.
delegate void Tick(int hours, int minutes, int seconds);
class Ticker
{
public event Tick tick;
⋮
}
class Clock
{
⋮
public void Start()
{
pulsed.tick += new Tick(this.RefreshTime);
}
⋮
private void RefreshTime(int hours, int minutes, int seconds)
{
⋮
}
private Ticker pulsed = new Ticker();
}
هنگاميكه رخداد pulsed.tick اجرا ميشود، تمامي delegate هاي مرتبط با آن نيز فراخواني ميشوند كه در اينجا يكي از آنها RefreshTime است. (به مثال موجود در بخش delegate رجوع نماييد.)
خارج شدن از ليست يك رخداد
همانطور كه با استفاده از عملگر += ميتوان delegate ي را به يك رخداد افزور، با استفاده از عملگر -= نيز ميتوان delegate خاصي را از ليست اعضاي يك رخداد خارج نمود.
class Clock
{
⋮
public void Stop()
{
pulsed.tick -= new Tick(this.RefreshTime);
}
private void RefreshTime(int hours, int minutes, int seconds)
{
⋮
}
private Ticker pulsed = new Ticker();
}
نكته : همانطور كه ميدانيد، عملگرهاي += و -= بر پاية دو عملگر اصلي + و – ايجاد شدهاند. از اينرو در مورد delegate ها نيز ميتوان از عملگر + استفاده نمود. استفاده از عملگر + براي delegate ها باعث ايجاد delegate جديدي ميشود كه به هنگام فراخواني هر دو delegate را به هم فراميخواند.
فراخواني يك رخداد
يك رخداد نيز همانند delegate، با استفاده از دو پرانتز فراخواني ميگردد. پس از اينكه رخدادي فراخواني شد، كليه delegate هاي مرتبط با آن بترتيب فراخواني ميشوند. براي مثال در اينجا كلاس Ticker را در نظر بگيريد كه داراي متد private با نام Notify است كه رخداد tick را فرا ميخواند :
class Ticker
{
public event Tick tick;
⋮
private void Notify(int hours, int minutes, int seconds)
{
if (tick != null)
{
tick(hours, minutes, seconds);
}
}
⋮
}
نكته مهم : توجه كنيد كه در مثال فوق چك كردن null نبودن رخداد tick ضروري است، چراكه فيلد رخداد بطور ضمني null در نظر گرفته ميشود و تنها زماني مقداري به غير null ميگيرد كه delegate ي به آن مرتبط شده باشد. در صورت فراخواني رخداد null، خطاي NullReferenceException روي خواهد داد.
رخدادها داراي سطح امنيتي داخلي بسيار بالايي هستند. رخدادي كه بصورت public اعلان ميشود، تنها از طريق متدها يا عناصر داخل همان كلاس قابل دسترسي است. بعنوان مثال، tick رخدادي درون كلاس Ticker است، از اينرو تنها متدهاي درون Ticker ميتوانند tick را فرا بخوانند.
class Example
{
static void Main()
{
Ticker pulsed = new Ticker();
pulsed.tick(12, 29, 59); // خطاي زمان كامپايل رخ ميدهد
}
}
مثالي پيشرفته از استفادة رخدادها در فرمهاي ويندوز
حال كه تا حدودي با رخدادها و ساختار آنها آشنا شديد، در اين قسمت قصد دارم تا مقداري دربارة استفاده رخدادها در فرمهاي ويندوز و GUI ها صحبت نمايم. هر چند تا كنون كليه برنامهها و مطالبي كه مشاهده كردهايد مبتني بر Console بودهاند، اما به علت استفاده بيشمار رخدادها در فرمهاي ويندوز و برنامههاي مبتني بر GUI، لازم ديدم تا مطالبي نيز در اين باره بيان كنم. هر چند فرمهاي ويندوز و GUI مطالبي هستند كه خود نياز به بحث و بررسي دقيق دارند و انشا ا... در رئوس آتي سايت مورد بررسي قرار خواهند گرفت. درصورتيكه مطالب اين قسمت براي شما دشوار و يا گنگ بود نگران و يا ناراحت نشويد چرا كه فعلاً براي يادگيري اين مطالب آنهم بدون مقدمه اندكي زود است، بيشتر هدف من از اين بخش آشنا شدن شما با كاربردهاي پيشرفتهتر رخدادها در برنامهنويسي بوده است.
كلاسهاي GUI مربوط به .Net Framework بطور گستردهاي از رخدادها استفاده مينمايند. در مثالي كه در اينجا مورد بررسي قرار ميدهيم، برنامهاي وجود دارد كه داراي يك فرم به همراه دو دكمه (Button) بر روي آن است. اين دو دكمه بوسيلة دو فيلد از نوع Button ايجاد ميشوند. (Button عضو System.Windows.Forms است). كلاس Button از كلاس Control ارثبري ميكند و داراي رخدادي با نام Click از نوع EventHandler است. به مثال توجه نماييد.
namespace System
{
public delegate void EventHandler(object sender, EventArgs args);
public class EventArgs
{
⋮
}
}
namespace System.Windows.Forms
{
public class Control :
{
public event EventHandler Click;
⋮
}
public class Button : Control
{
⋮
}
}
توجه نماييد كه كد فوق، كد مربوط به namespace مربوط به System است كه نحوة پيادهسازي آنرا نشان ميدهد. همانطور كه ملاحظه مينماييد، درون System، delegate ي با نام EventHandler تعريف شده است. در زير اين namespace، اعلان System.Windows.Forms نيز آورده شده تا نحوة اعلان رخداد Click و ارثبري كلاس Button از كلاس Control نيز مشخص شود.
پس از اينكه بر روي دكمهاي واقع در فرم ويندوز كليك كنيد، Button بطور خودكار رخداد Click را فرا ميخواند. هدايت اين پروسه باعث ميشود تا بتوان به سادگي delegate ي براي كنترل اين رخداد ايجاد نمود. در مثالي كه در زير مشاهده ميكنيد، دكمهاي با نام Okay، متدي بنام Okay_Click و رخدادي جهت اتصال Okay به متد Okay_Click وجود دارد.
class Example : System.Windows.Forms.Form
{
private System.Windows.Forms.Button okay;
⋮
public Example()
{
this.okay = new System.Windows.Forms.Button();
this.okay.Click += new System.EventHandler(this.okay_Click);
⋮
}
private void okay_Click(object sender, System.EventsArgs args)
{
⋮
}
}
همانطور كه مشاهده ميكنيد، كلاس Example از System.Windows.Forms.Form مشتق ميشود، از اينرو تمامي خواص آن را به ارث ميبرد. Okay نيز از نوع Button اعلان شده است. درون سازندة (Constructor) كلاس Example، متد Okay.Click به رخداد افزوده شده و مرجع this.Okay.Click نيز، متد مورد نظر را تعيين نموده است. همانطور كه گفته شد، EventHandler نيز delegate مورد نظر در اين مثال است. درون متد Okay_Click نيز ميتوان كد خاصي را قرار داد تا عمل مورد نظر را انجام دهد. پس از كليك كردن بر روي دكمة Okay، عمل مورد نظري كه درون متد Okay_Click قرار داده شده، اجرا ميشود.
اين كد، شبيه به كدهايي است كه توسط محيطهاي برنامهسازي نظير Visual Studio.Net و يا C#Builder بطور خودكار توليد ميشوند و تنها كافيست تا شما كد مربوط به Okay_Click را درون آن وارد نماييد.
رخدادهايي كه توسط كلاسهاي GUI توليد ميشوند همواره از يك الگوي خاص پيروي ميكنند. اين رخدادها همواره از نوع delegate ي هستند كه مقدار بازگشتي ندارد (void) و داراي دو آرگومان است. آرگومان اول هميشه فرستندة رخداد و آرگومان دوم هميشه آرگومان EventArgs يا كلاس مشتق شده از EventArgs است.
آرگومان sender به شما اين امكان را ميدهد تا از يك delegate براي چندين رخداد استفاده نماييد. (بعنوان مثال براي چندين دكمه.)
نكاتي چند درباره delegate ها و event ها
Delegate ها بطور ضمني از System.Delegate ارثبري ميكنند. Delegate حاوي متدها، property ها و عملگرهايي است كه ميتوان آنها را بعنوان پارامتر به متدهاي ديگر ارسال نمود. همچنين به دليل اينكه System.Delegate بخشي از .Net Framework است، از اينرو delegate هاي ايجاد شده در C# را ميتوان در زبانهاي ديگري نظير Visual Basic.Net نيز استفاده نمود.
هنگام اعلان پارامترها براي delegate، حتماً بايد براي آنها نام در نظر بگيريد و فقط به مشخص كردن نوع پارامترها بسنده نكنيد.
رخدادها عناصر بسيار مفيد و پر استفادهاي هستند كه با بكارگيري delegate ها بسيار قدرتمند ظاهر ميشوند. بدست آوردن مهارت در ايجاد و استفاده از آنها نياز به تمرين و تفكر بسيار دارد.
مطالب اين درس در اينجا به پايان رسيد. اميدوارم مورد قبول شما بوده باشد. خواهشمندام نظرات وپيشنهادات ارزندة خود را براي من ايميل كنيد تا از آنها در جهت بهبود مطالب سايت استفاده گردد.
نكته مهم قبل از مطالعه اين درس
توجه نماييد، delegate ها و رخدادها بسيار با يكديگر در تعاملاند، از اينرو در برخي موارد، قبل از آموزش و بررسي رخدادها، به ناچار، از آنها نيز استفاده شده و يا به آنها رجوع شده است. رخدادها در قسمت انتهايي اين درس مورد بررسي قرار ميگيرند، از اينرو در صورتيكه در برخي موارد دچار مشكل شديد و يا درك مطلب برايتان دشوار بود، ابتدا كل درس را تا انتها مطالعه نماييد و سپس در بار دوم با ديدي جديد به مطالب و مفاهيم موجود در آن نگاه كنيد. در اغلب كتابهاي آموزشي زبان C# نيز ايندو مفهوم با يكديگر آورده شدهاند ولي درك رخدادها مستلزم درك و فراگيري كامل delegate هاست، از اينرو مطالب مربوط به delegate ها را در ابتدا قرار دادهام.
هدف ما در اين درس به شرح زير است :
مقدمه
درك اينكه يك delegate چيست؟
اعلان و پيادهسازي delegate ها
درك سودمندي delegate ها
حل مسئله بدون استفاده از delegate
حل مسئله با استفاده از delegate
اعلان delegate ها (بخش پيشرفته)
فراخواني delegate ها (بخش پيشرفته)
ايجاد نمونههاي جديد از يك delegate (بخش پيشرفته)
درك اينكه يك رخداد يا يك event چيست؟
اعلان رخدادها
نكات و توضيحات پيشرفته
ثبت شدن در يك رخداد
لغو عضويت در يك رخداد
فراخواني رخدادها
مثالي پيشرفته از استفاده رخدادها در فرمهاي ويندوز
نكات كليدي درباره رخدادها و delegate ها
منابع مورد استفاده
طي درسهاي گذشته، چگونگي ايجاد و پيادسازي انواع مرجعي (Reference Type) را با استفاده از ساختارهاي زبان C#، يعني كلاسها (Class) و واسطها (Interface)، فرا گرفتيد. همچنين فرا گرفتيد كه با استفاده از اين انواع مرجعي، ميتوانيد نمونههاي جديدي از اشياء را ايجاد كرده و نيازهاي توسعه نرمافزار خود را تامين نماييد. همانطور كه تا كنون ديديد، با استفاده از كلاسها قادر به ساخت اشيائي هستيد كه داراي صفات (Attribute) و رفتارهاي (Behavior) خاصي بودند. با استفاده از واسطها، يكسري از صفات و رفتارها را تعريف ميكرديم تا فرم كلي داشته باشيم و تمام اشياء خود به پيادهسازي اين صفا و رفتارها ميپرداختند. در اين درس با يكي ديگر از انواع مرجعي (Reference Type) در زبان C# آشنا خواهيد شد.
مقدمهاي بر رخدادها و delegate ها
در گذشته، پس از اجراي يك برنامه، برنامه مراحل اجراي خود را مرحله به مرحله اجرا مينمود تا به پايان برسد. در صورتيكه نياز به ارتباط و تراكنش با كاربر نيز وجود داشت، اين امر محدود و بسيار كنترل شده صورت ميگرفت و معمولاً ارتباط كاربر با برنامه تنها پر كردن و يا وارد كردن اطلاعات خاصي در فيلدهايي مشخص بود.
امروزه با پبشرفت كامپيوتر و گسترش تكنولوژيهاي برنامه نويسي و با ظهور رابطهاي كاربر گرافيكي (GUI) ارتباط بين كاربر و برنامه بسيار گسترش يافته و ديگر اين ارتباط محدود به پر كردن يكسري فيلد نيست، بلكه انواع عمليات از سوي كاربر قابل انجام است. انتخاب گزينهاي خاص در يك منو، كليك كردن بر روي دكمهها براي انجام عملياتي خاص و ... . رهيافتي كه امروزه در برنامهنويسي مورد استفاده است، تحت عنوان "برنامهنويسي بر پايه رخدادها" (Event-Based Programming) شناخته ميشود. در اين رهيافت برنامه همواره منتظر انجام عملي از سوي كاربر ميماند و پس از انجام عملي خاص، رخداد مربوط به آن را اجرا مينمايد. هر عمل كاربر باعث اجراي رخدادي ميشود. در اين ميان برخي از رخدادها بدون انجام عملي خاص از سوي كاربر اجرا ميشوند، همانند رخدادهاي مربوط به ساعت سيستم كه مرتباً در حال اجرا هستند.
رخدادها (Events) بيان اين مفهوم هستند كه در صورت اتفاق افتادن عملي در برنامه، كاري بايد صورت گيرد. در زبان C# مفاهيم Event و Delegate دو مفهوم بسيار وابسته به يكديگر هستند و با يكديگر در تعامل ميباشند. براي مثال، مواجهه با رخدادها و انجام عمل مورد نظر در هنگام اتفاق افتادن يك رخداد، نياز به يك event handler دارد تا در زمان بروز رخداد، بتوان به آن مراجعه نمود. Event handler ها در C# معمولاً با delegate ها ساخته ميشوند.
از delegate ، ميتوان به عنوان يك Callback ياد نمود، بدين معنا كه يك كلاس ميتواند به كلاسي ديگر بگويد : "اين عمل خاص را انجام بده و هنگاميكه عمليات را انجام دادي منرا نيز مطلع كن". با استفاده از delegate ها، همچنين ميتوان متدهايي تعريف نمود كه تنها در زمان اجرا قابل دسترسي باشند.
Delegate
Delegate ها، يكي ديگر از انواع مرجعي زبان C# هستند كه با استفاده از آنها ميتوانيد مرجعي به يك متد داشته باشيد، بدين معنا كه delegate ها، آدرس متدي خاص را در خود نگه ميدارند. در صورتيكه قبلاً با زبان C برنامهنويسي كردهايد، حتماً با اين مفهوم آشنايي داريد. در زبان C اين مفهوم با اشارهگرها (pointer) بيان ميشود. اما براي افرادي كه با زبانهاي ديگري برنامهنويسي ميكردهاند و با اين مفهوم مانوس نيستند، شايد اين سوال مطرح شود كه چه نيازي به داشتن آدرس يك متد وجود دارد. براي پاسخ به اين سوال اندكي بايد تامل نماييد.
بطور كلي ميتوان گفت كه delegate نوعي است شبيه به متد و همانند آن نيز رفتار ميكند. در حقيقت delegate انتزاعي (Abstraction) از يك متد است. در برنامهنويسي ممكن به شرايطي برخورد كرده باشيد كه در آنها ميخواهيد عمل خاصي را انجام دهيد اما دقيقاً نميدانيد كه بايد چه متد يا شيءاي را براي انجام آن عمل خاص مورد استفاده قرار دهيد. در برنامههاي تحت ويندوز اين گونه مسائل مشهودتر هستند. براي مثال تصور كنيد در برنامه شما، دكمهاي قرار دارد كه پس از فشار دادن اين دكمه توسط كاربر شيءاي يا متدي بايد فراخواني شود تا عمل مورد نظر شما بر روي آن انجام گيرد. ميتوان بجاي اتصال اين دكمه به شيء يا متد خاص، آنرا به يك delegate مرتبط نمود و سپس آن delegate را به متد يا شيء خاصي در هنگام اجراي برنامه متصل نمود.
ابتدا، به نحوه استفاده از متدها توجه نماييد. معمولاً، براي حل مسايل خود الگوريتمهايي طراحي مينائيم كه اين الگوريتمهاي كارهاي خاصي را با استفاده از متدها انجام ميدهد، ابتدا متغيرهايي مقدار دهي شده و سپس متدي جهت پردازش آنها فراخواني ميگردد. حال در نظر بگيريد كه به الگوريتمي نياز داريد كه بسيار قابل انعطاف و قابل استفاده مجدد (reusable) باشد و همچنين در شرايط مختلف قابليتهاي مورد نظر را در اختيار شما قرار دهد. تصور كنيد، به الگوريتمي نياز داريد كه از نوعي از ساختمان داده پشتيباني كند و همچنين ميخواهيد اين ساختمان داده را در مواردي مرتب (sort) نماييد، بعلاوه ميخواهيد تا اين ساختمان داده از انواع مختلفي تشكيل شده باشد. اگر انواع موجود در اين ساختمان داده را ندانيد، چكونه ميخواهيد الگوريتمي جهت مقايسه عناصر آن طراحي كنيد؟ شايد از يك حلقه if/then/else و يا دستور switch براي اين منظور استفاده كنيد، اما استفاده از چنين الگوريتمي محدوديتي براي ما ايجاد خواهد كرد. روش ديگر، استفاده از يك واسط است كه داراي متدي عمومي باشد تا الگوريتم شما بتواند آنرا فراخواني نمايد، اين روش نيز مناسب است، اما چون مبحث ما در اين درس delegate ها هستند، ميخواهيم مسئله را از ديدگاه delegate ها مورد بررسي قرار دهيم. روش حل مسئله با استفاده از آنها اندكي متفاوت است.
روش ديگر حل مسئله آنست كه، ميتوان delegate ي را به الگوريتم مورد نظر ارسال نمود و اجازه داد تا متد موجود در آن،عمل مورد نظر ما را انجام دهد. چنين عملي در مثال 1-14 نشان داده شده است.
(به صورت مسئله توجه نماييد : ميخواهيم مجموعهاي از اشياء را كه در يك ساختمان داده قرار گرفتهاند را مرتب نمائيم. براي اينكار نياز به مقايسه اين اشياء با يكديگر داريم. از آنجائيكه اين اشياء از انواع (type) مختلف هستند به الگوريتمي نياز داريم تا بتواند مقايسه بين اشياء نظير را انجام دهد. با استفاده از روشهاي معمول اين كار امكان پذير نيست، چراكه نميتوان اشيائئ از انواع مختلف را با يكديگر مقايسه كرد. براي مثال شما نميتوانيد نوع عددي int را با نوع رشتهاي string مقايسه نماييد. به همين دليل با استفاده از delegate ها به حل مسئله پرداختهايم. به مثال زير به دقت توجه نماييد تا بتوانيد به درستي مفهوم delegate را درك كنيد.)
مثال 1-14 : اعلان و پيادهسازي يك delegate
using System;
// در اينجا اعلان ميگردد. delegate
public delegate int Comparer(object obj1, object obj2);
public class Name
{
public string FirstName = null;
public string LastName = null;
public Name(string first, string last)
{
FirstName = first;
LastName = last;
}
// delegate method handler
public static int CompareFirstNames(object name1, object name2)
{
string n1 = ((Name)name1).FirstName;
string n2 = ((Name)name2).FirstName;
if (String.Compare(n1, n2) > 0)
{
return 1;
}
else if (String.Compare(n1, n2) < 0)
{
return -1;
}
else
{
return 0;
}
}
public override string ToString()
{
return FirstName + " " + LastName;
}
}
class SimpleDelegate
{
Name[] names = new Name[5];
public SimpleDelegate()
{
names[0] = new Name("Meysam", "Ghazvini");
names[1] = new Name("C#", "Persian");
names[2] = new Name("Csharp", "Persian");
names[3] = new Name("Xname", "Xfamily");
names[4] = new Name("Yname", "Yfamily");
}
static void Main(string[] args)
{
SimpleDelegate sd = new SimpleDelegate();
// delegate ساخت نمونهاي جديد از
Comparer cmp = new Comparer(Name.CompareFirstNames);
Console.WriteLine("\nBefore Sort: \n");
sd.PrintNames();
sd.Sort(cmp);
Console.WriteLine("\nAfter Sort: \n");
sd.PrintNames();
}
public void Sort(Comparer compare)
{
object temp;
for (int i=0; i < names.Length; i++)
{
for (int j=i; j < names.Length; j++)
{
//همانند يك متد استفاده ميشود compare از
if ( compare(names[i], names[j]) > 0 )
{
temp = names[i];
names[i] = names[j];
names[j] = (Name)temp;
}
}
}
}
public void PrintNames()
{
Console.WriteLine("Names: \n");
foreach (Name name in names)
{
Console.WriteLine(name.ToString());
}
}
}
اولين اعلان در اين برنامه، اعلان delegate است. اعلان delegate بسيا رشبيه به اعلان متد است، با اين تفاوت كه داراي كلمه كليدي delegate در اعلان است و در انتهاي اعلان آن ";" قرار ميگيرد و نيز پيادهسازي ندارد. در زير اعلان delegate كه در مثال 1-14 آورده شده را مشاهده مينماييد :
public delegate int Comparer(object obj1, object obj2);
اين اعلان، مدل متدي را كه delegate ميتواند به آن اشاره كند را تعريف مينمايد. متدي كه ميتوان از آن بعنوان delegate handler براي Comparer استفاده نمود، هر متدي ميتواند باشد اما حتماً بايد پارامتر اول و دوم آن از نوع object بوده و مقداري از نوع int بازگرداند. در زير متدي كه بعنوان delegate handler در مثال 1-14 مورد استفاده قرار گرفته است، نشان داده شده است :
public static int ComparerFirstNames(object name1, object name2)
{
…
}
براي استفاده از delegate ميبايست نمونهاي از آن ايجاد كنيد. ايجاد نمونه جديد از delegate همانند ايجاد نمونهاي جديد از يك كلاس است كه به همراه پارامتري جهت تعيين متد delegate handler ايجاد ميشود :
Comparer cmp = new Comparer(Name.ComparerFirstName);
در مثال 1-14، cmp بعنوان پارامتري براي متد Sort() مورد استفاده قرار گرفته است. به روش ارسال delegate به متد Sort() توجه نماييد :
sd.Sort(cmp);
با استفاده از اين تكنيك، هر متد delegate handler به سادگي در زمان اجرا به متد Sort() قابل ارسال است. براي مثال ميتوان handler ديگري با نام CompareLastNames() تعريف كنيد، نمونه جديدي از Comparer را با اين پارامتر ايجاد كرده و سپس آنرا به متد Sort() ارسال نماييد.
درك سودمندي delegate ها
براي درك بهتر delegate ها به بررسي يك مثال ميپردازيم. در اينجا اين مثال را يكبار بدون استفاده از delegate و بار ديگر با استفاده از آن حل كرده و بررسي مينمائيم. مطالب گفته شده در بالا نيز به نحوي مرور خواهند شد. توجه نماييد، همانطور كه گفته شد delegate ها و رخدادها بسيار با يكديگر در تعاملاند، از اينرو در برخي موارد به ناچار از رخدادها نيز استفاده شده است. رخدادها در قسمت انتهايي اين درس آورده شدهاند، از اينرو در صورتيكه در برخي موارد دچار مشكل شديد و يا درك مطلب برايتان دشوار بود، ابتدا كل درس را تا انتها مطالعه نماييد و سپس در بار دوم با ديدي جديد به مطالب و مفاهيم موجود در آن نگاه كنيد. در اغلب كتابهاي آموزشي زبان C# نيز ايندو مفهوم با يكديگر آورده شدهاند ولي درك رخدادها مستلزم درك و فراگيري كامل delegate هاست، از اينرو مطالب مربوط به delegate ها را در ابتدا قرار دادهام.
حل مسئله بدون استفاده از delegate
فرض كنيد، ميخواهيد برنامه بنويسيد كه عمل خاصي را هر يك ثانيه يكبار انجام دهد. يك روش براي انجام چنين عملي آنست كه، كار مورد نظر را در يك متد پيادهسازي نماييد و سپس با استفاده از كلاسي ديگر، اين متد را هر يك ثانيه يكبار فراخواني نمائيم. به مثال زير توجه كنيد :
class Ticker
{
⋮
public void Attach(Subscriber newSubscriber)
{
subscribers.Add(newSubscriber);
}
public void Detach(Subscriber exSubscriber)
{
subscribers.Remove(exSubscriber);
}
// هر ثانيه فراخواني ميگردد Notify
private void Notify()
{
foreach (Subscriber s in subscribers)
{
s.Tick();
}
}
⋮
private ArrayList subscribers = new ArrayList();
}
class Subscriber
{
public void Tick()
{
⋮
}
}
class ExampleUse
{
static void Main()
{
Ticker pulsed = new Ticker();
Subscriber worker = new Subscriber();
pulsed.Attach(worker);
⋮
}
}
اين مثال مطمئناً كار خواهد كرد اما ايدآل و بهينه نيست. اولين مشكل آنست كه كلاس Ticker بشدت وابسته به Subscriber است. به بيان ديگر تنها نمونههاي جديد كلاس Subscriber ميتوانند از كلاس Ticker استفاده نمايند. اگر در برنامه كلاس ديگري داشته باشيد كه بخواهيد آن كلاس نيز هر يك ثانيه يكبار اجرا شود، ميبايست كلاس جديدي شبيه به Ticker ايجاد كنيد. براي بهينه كردن اين مسئله ميتوانيد از يك واسط (Interface) نيز كمك بگيريد. براي اين منظور ميتوان متد Tick را درون واسطي قرار داد و سپس كلاس Ticker را به اين واسط مرتبط نمود.
interface Tickable
{
void Tick();
}
class Ticker
{
public void Attach(Tickable newSubscriber)
{
subscribers.Add(newSubscriber);
}
public void Detach(Tickable exSubscriber)
{
subscribers.Remove(exSubscriber);
}
// هر ثانيه فراخواني ميگردد Notify
private void Notify()
{
foreach (Tickable t in subscribers)
{
t.Tick();
}
}
⋮
private ArrayList subscribers = new ArrayList();
}
اين راه حل اين امكان را براي كليه كلاسها فراهم مينمايد تا واسط Tickable را پيادهسازي كنند.
class Clock : Tickable
{
⋮
public void Tick()
{
⋮
}
⋮
}
class ExampleUse
{
static void Main()
{
Ticker pulsed = new Ticker();
Clock wall = new Clock();
pulsed.Attach(wall);
⋮
}
}
حال به بررسي همين مثال با استفاده از delegate خواهيم پرداخت.
حل مسئله با استفاده از delegate
استفاده از واسطها در برنامهها، مطمئناً روشي بسيار خوب است، اما كامل نبوده اشكالاتي دارد. مشكل اول آنست كه اين روش بسيار كلي و عمومي است. تصور نماييد ميخواهيد از تعداد زيادي از سرويسها استفاده نماييد(بعنوان مثال در برنامههاي مبتني بر GUI. در اينگونه برنامهها هجم عظيمي از رخدادها وجود دارند كه ميبايست با تمامي آنها در ارتباط باشيد.) مشكل ديگر آنست كه استفاده از واسط، بدين معناست كه متد Tick بايد متدي public باشد، از اينرو هر كدي ميتواند Clock.Tick را در هر زماني فراخواني نمايد. روش مناسب تر آنست كه مطمئن شويم تنها اعضايي خاص قادر به فراخواني و دسترسي به Clock.Tick هستند. با استفاده از delegate تمامي اين امكانات براي ما فراهم خواهد شد و برنامههايي با ايمني بالاتر و پايدارتر ميتوانيم داشته باشيم.
اعلان Delegate
در مثال ما، متد Tick از واسط Tickable از نوع void بود و هيچ پارامتري دريافت نميكرد :
interface Tickable
{
void Tick();
}
براي اين متد ميتوان delegate ي تعريف نمود كه ويژگيهاي آنرا داشته باشد :
delegate void Tick();
همانطور كه قبلاً نيز گفته شد، اين عمل نوع جديدي را ايجاد مينمايد كه ميتوان از آن همانند ساير انواع استفاده نمود. مثلاً ميتوان آنرا بعنوان پارامتري براي يك متد در نظر گرفت :
void Example(Tick param)
{
⋮
}
فراخواني delegate
قدرت و توانايي delegate زماني مشهود ميگردد كه ميخواهيد از آن استفاده نماييد. براي مثال، با متغير param در مثال قبل چكار ميتوانيد انجام دهيد؟ اگر param متغيري از نوع int بود، از مقدار آن استفاده ميكرديد و با استفاده از عملگرهايي نظير +، - و يا عملگرهاي مقايسهاي، عملي خاص را بر روي آن انجام ميداديد. اما حال كه param متغيري از نوع int نيست، چه ميكنيد؟ متغير param يك delegate است و همانطور كه گفته شد، delegate انتزاعي از يك متد است، پس هر عملي كه متد انجام ميدهد، delegate نيز ميتواند انجام دهد. با استفاده از پرانتز، ميتوان از delegate استفاده نمود :
void Example(Tick param)
{
param();
}
نكته : همانطور كه اشاره شد، delegate يكي از انواع مرجعي است از اينرو مقدار آن ميتواند برابر با Null باشد. در مثال فوق، اگر مقدار param برابر با Null باشد، كامپايلر خطاي NullReferenceException را ايجاد مينمايد.
همانند متدها، delegate ها بايد بطور كامل و صحيح فراخواني گردند. با توجه به اعلان Tick، در زمان فراخواني اين delegate، مثلاً param، بايد توجه داشت كه هيچ پارامتري را نميتوان به آن ارسال نمود و نميتوان آنرا به متغيري نسبت داد چراكه اين delegate بصورت void اعلان شده و مقدار بازگشتي ندارد.
void Example(Tick param)
{
param(42); // خطاي زمان كامپايل رخ ميدهد
int hhg = param(); // خطاي زمان كامپايل رخ ميدهد
Console.WriteLine(param()); // خطاي زمان كامپايل رخ ميدهد
}
توجه نماييد كه delegate را به هر نحوي ميتوانيد اعلان نماييد. براي مثال به نسخة ديگري از Tick توجه كنيد :
delegate void Tick(int hours, int minutes, int seconds);
اما به ياد داشته باشيد كه همانند متد، در هنگام استفاده از آن بايد پارامترهاي صحيح به آن ارسال نماييد :
void Example(Tick method)
{
method(12, 29, 59);
}
با استفاده از delegate ميتوانيد كلاس Ticker را پيادهسازي كنيد :
delegate void Tick(int hours, int minutes, int seconds);
class Ticker
{
⋮
public void Attach(Tick newSubscriber)
{
subscribers.Add(newSubscriber);
}
public void Detach(Tick exSubscriber)
{
subscribers.Remove(exSubscriber);
}
private void Notify(int hours, int minutes, int seconds)
{
foreach (Tick method in subscribers)
{
method(hours, minutes, seconds);
}
}
⋮
private ArrayList subscribers = new ArrayList();
}
ساخت نمونههاي جديد از يك delegate
آخرين كاري كه بايد انجام دهيد، ايجاد نمونههاي جديد از delegate ساخته شده است. يك نمونة جديد از يك delegate، تنها انتزاعي از يك متد است كه با نامگذاري آن متد ايجاد ميشود.
class Clock
{
⋮
public void RefreshTime(int hours, int minutes, int seconds)
{
⋮
}
⋮
}
با توجه به ساختار Tick، ملاحظه مينماييد كه متد RefreshTime كاملاً با اين delegate همخواني دارد :
delegate void Tick(int hours, int minutes, int seconds);
و اين بدين معناست كه ميتوان نمونة جديد از Tick ايجاد كرد كه انتزاعي از فراخواني RefreshTime در شيء خاصي از Clock است.
Clock wall = new Clock();
⋮
Tick m = new Tick(wall.RefreshTime);
حال كه m، ايجاد شد، ميتوانيد از آن بصورت زير استفاده نماييد :
m(12, 29, 59);
اين دستور در حقيقت كار دستور زير را انجام ميدهد (چون m دقيقاً انتزاع آن است) :
wall.RefreshTime(12, 29, 59);
همچنين ميتوانيد m را بعنوان پارامتر به متدي ارسال نماييد. حال تمام چيزهايي را كه براي حل مسئله با استفاده از delegate بدانها نياز داشتيم را بررسي كرديم. در زير مثالي را مشاهده ميكنيد كه كلاسهاي Ticker و Clock را به يكديگر مرتبط نموده است. در اين مثال از واسط استفاده نشده و متد RefreshTime، متدي private است :
delegate void Tick(int hours, int minutes, int seconds);
⋮
class Clock
{
⋮
public void Start()
{
ticking.Attach(new Tick(this.RefreshTime));
}
public void Stop()
{
ticking.Detach(new Tick(this.RefreshTime));
}
private void RefreshTime(int hours, int minutes, int seconds)
{
Console.WriteLine("{0}:{1}:{2}", hours, minutes, seconds);
}
private Ticker ticking = new Ticker();
}
با اندكي تامل و صرف وقت ميتوانيد delegate را بطور كامل درك نماييد.
رخدادها (Events)
در برنامههاي Console ، برنامه منتظر ورود اطلاعات يا دستوراتي از سوي كاربر ميماند و با استفاده از اين اطلاعات كار مورد نظر را انجام ميدهند. اين روش برقراري ارتباط با كاربر، روشي ناپايدار و غير قابل انعطاف است. در مقابل برنامههاي Console، برنامههاي مدرن وجود دارند كه با استفاده از GUI با كاربر در ارتباطند و بر پايه رخدادها بنا شدهاند (Event-Based)، بدين معنا كه رخدادي (منظور از رخداد اتفاقي است كه در سيستم يا محيط برنامه صورت ميگيرد.) در سيستم روي ميدهد و بر اساس اين رخداد عملي در سيستم انجام ميشود. در برنامههاي تحت ويندوز، نيازي به استفاده از حلقههاي متعدد جهت منتظر ماندن براي ورودي از كاربر نيست، بلكه با استفاده از رخدادها، تراكنش بين سيستم و كاربر كنترل ميشود.
يك event در زبان C#، عضوي از كلاس است، كه در صورت بروز رخداد خاصي، فعال ميشود و عملي را انجام ميدهد. معمولاً براي فعال شده event از دو عبارت fires و raised استفاده ميشود. هر متدي كه بخواهد، ميتواند در ليست رخداد ثبت شده و به محض اتفاق افتادن آن رخداد، از آن مطلع گردد.
بطور كلي ميتوان گفت كه يك رخداد همانند يك فيلد اعلان ميشود با اين تفاوت مهم كه نوع آنها حتماٌ بايد يك delegate باشد.
Delegate و رخدادها در كنار يكديگر كار ميكنند تا قابليتهاي يك برنامه را افزايش دهند. اين پروسه با شروع يك كلاس كه يك رخداد را تعريف ميكند، آغاز ميشود. هر كلاسي، كه اين رخداد را درون خود داشته باشد، در آن رخداد ثبت شده است و ميتواند متدي را به آن رخداد تخصيص دهد. اين عمل با استفاده از delegate ها صورت ميپذيرد، بدين معني كه delegate متدي را كه براي رخداد ثبت ميشود را تعيين مينمايد. Delegate ها ميتوانند هر يك از delegate هاي از پيش تعريف شدة .Net و يا هر delegate ي باشند كه توسط كاربر تعريف شده است. بطور كلي، delegate ي را به رخدادي تخصيص ميدهيم تا متدي را كه بهنگام روي دادن رخداد فراخواني ميشود، معين گردد. مثال زير روش تعريف رخداد را نشان ميدهد.
مثال 2-14 : اعلان و پيادهسازي رخدادها
using System;
public delegate void MyDelegate();
class Listing14-2
{
public static event MyDelegate MyEvent;
static void Main()
{
MyEvent += new MyDelegate(CallbackMethod);
// فراخواني رخداد
MyEvent();
Console.ReadLine();
}
public static void CallbackMethod()
{
Console.WriteLine("CallbackMethod.");
}
}
در اين مثال، ابتدا اعلان يك delegate ديده ميشود. درون كلاس، رخدادي با نام MyEvent و از نوع MyDelegate تعريف شده است. در متد Main() نيز مرجع جديدي به رخداد MyEvent افزوده شده است. همانطور كه در اين مثال نيز مشاهده ميكنيد، delegate ها تنها با استفاده از += ميتوانند به رخدادها افزوده شوند. در اين مثال هر گاه MyEvent فراخواني شود، متد CallbackMethod اجرا ميشود چراكه با استفاده از مرجع delegate به رخداد مرتبط شده است. (يا در اصطلاح در رخداد ثبت شده است.)
مثال فوق را بدون استفاده از رخداد نيز ميتوان نوشت. اين نسخه از مثال 2-14 كه تنها در آن از delegate استفاده شده در زير آورده شده است :
using System;
public delegate void MyDelegate();
class UsingDelegates
{
static void Main()
{
MyDelegate del = new MyDelegate(CallbackMethod);
// delegate فراخواني
del();
Console.ReadLine();
}
public static void CallbackMethod()
{
Console.WriteLine("CallbackMethod.");
}
}
بايد توجه كنيد كه موارد كاربرد رخدادها بيشتر در برنامههاي تحت ويندوز نمايان ميشود و در اينجا شايد وجود آنها در برنامه براي شما مشهود نباشد. در آينده، به بررسي برنامهنويسي فرمهاي ويندوز نيز خواهيم رسيد و در آنجا به طور مفصل درباره event ها و delegate ها مجدداً بحث خواهيم نمود.
بطور خلاصه ميتوان گفت، با استفاده از delegate ها روشي براي ايجاد دسترسي به متدها بط.ور پويا را فراهم نموديم. با استفاده از رخدادها نيز، در صورت بروز اتفاقي خاص، عملي خاص انجام ميگيرد. اين عمل معمولاٌ با استفاده از يك delegate كه مرجعي به يك متد در خود دارد انجام ميگيرد.
توضيحات پيشرفته :
در انتهاي اين درس ميخواهم توضيحات پيشرفته تري را نيز در اختيار شما قرار دهم. در قسمت مربوط به delegate ها در همين درس، مثالي مطرح شد كه در آن delegate ي با نام Tick وجود داشت. اعلان اين delegate به صورت زير بود :
delegate void Tick(int hours, int minutes, int seconds);
حال ميخواهيم به اين مثال يك رخداد نيز اضافه كنيم. در زير رخداد tick از نوع Tick اعلان شده است :
class Ticker
{
public event Tick tick;
⋮
}
بايد توجه نماييد كه يك رخداد بطور خودكار ليست اعضاي خود را مديريت ميكند و نيازي به استفاده از يك مجموعه، مانند آرايه، براي مديريت اعضاي مرتبط با آن نيست.
نكته : يك رخداد بطور خودكار خود را تخصيص دهي ميكند و نيازي به ساخت نمونة جديد از روي يك رخداد وجود ندارد.
عضو شدن در يك رخداد (تبث شدن در يك رخداد)
براي افزودن delegate جديد به يك رخداد كافيست تا از عملگر += استفاده نماييم. مثال زير كلاس Clock را نشان ميدهد كه در آن فيلدي از نوع Ticker با نام pulsed وجود دارد. كلاس Ticker داراي رخداد tick از نوع delegate ي بنام Tick است. متد Clock.Start، delegate ي از نوع Tick را با استفاده از عملگر += به pulsed.tick ميافزايد.
delegate void Tick(int hours, int minutes, int seconds);
class Ticker
{
public event Tick tick;
⋮
}
class Clock
{
⋮
public void Start()
{
pulsed.tick += new Tick(this.RefreshTime);
}
⋮
private void RefreshTime(int hours, int minutes, int seconds)
{
⋮
}
private Ticker pulsed = new Ticker();
}
هنگاميكه رخداد pulsed.tick اجرا ميشود، تمامي delegate هاي مرتبط با آن نيز فراخواني ميشوند كه در اينجا يكي از آنها RefreshTime است. (به مثال موجود در بخش delegate رجوع نماييد.)
خارج شدن از ليست يك رخداد
همانطور كه با استفاده از عملگر += ميتوان delegate ي را به يك رخداد افزور، با استفاده از عملگر -= نيز ميتوان delegate خاصي را از ليست اعضاي يك رخداد خارج نمود.
class Clock
{
⋮
public void Stop()
{
pulsed.tick -= new Tick(this.RefreshTime);
}
private void RefreshTime(int hours, int minutes, int seconds)
{
⋮
}
private Ticker pulsed = new Ticker();
}
نكته : همانطور كه ميدانيد، عملگرهاي += و -= بر پاية دو عملگر اصلي + و – ايجاد شدهاند. از اينرو در مورد delegate ها نيز ميتوان از عملگر + استفاده نمود. استفاده از عملگر + براي delegate ها باعث ايجاد delegate جديدي ميشود كه به هنگام فراخواني هر دو delegate را به هم فراميخواند.
فراخواني يك رخداد
يك رخداد نيز همانند delegate، با استفاده از دو پرانتز فراخواني ميگردد. پس از اينكه رخدادي فراخواني شد، كليه delegate هاي مرتبط با آن بترتيب فراخواني ميشوند. براي مثال در اينجا كلاس Ticker را در نظر بگيريد كه داراي متد private با نام Notify است كه رخداد tick را فرا ميخواند :
class Ticker
{
public event Tick tick;
⋮
private void Notify(int hours, int minutes, int seconds)
{
if (tick != null)
{
tick(hours, minutes, seconds);
}
}
⋮
}
نكته مهم : توجه كنيد كه در مثال فوق چك كردن null نبودن رخداد tick ضروري است، چراكه فيلد رخداد بطور ضمني null در نظر گرفته ميشود و تنها زماني مقداري به غير null ميگيرد كه delegate ي به آن مرتبط شده باشد. در صورت فراخواني رخداد null، خطاي NullReferenceException روي خواهد داد.
رخدادها داراي سطح امنيتي داخلي بسيار بالايي هستند. رخدادي كه بصورت public اعلان ميشود، تنها از طريق متدها يا عناصر داخل همان كلاس قابل دسترسي است. بعنوان مثال، tick رخدادي درون كلاس Ticker است، از اينرو تنها متدهاي درون Ticker ميتوانند tick را فرا بخوانند.
class Example
{
static void Main()
{
Ticker pulsed = new Ticker();
pulsed.tick(12, 29, 59); // خطاي زمان كامپايل رخ ميدهد
}
}
مثالي پيشرفته از استفادة رخدادها در فرمهاي ويندوز
حال كه تا حدودي با رخدادها و ساختار آنها آشنا شديد، در اين قسمت قصد دارم تا مقداري دربارة استفاده رخدادها در فرمهاي ويندوز و GUI ها صحبت نمايم. هر چند تا كنون كليه برنامهها و مطالبي كه مشاهده كردهايد مبتني بر Console بودهاند، اما به علت استفاده بيشمار رخدادها در فرمهاي ويندوز و برنامههاي مبتني بر GUI، لازم ديدم تا مطالبي نيز در اين باره بيان كنم. هر چند فرمهاي ويندوز و GUI مطالبي هستند كه خود نياز به بحث و بررسي دقيق دارند و انشا ا... در رئوس آتي سايت مورد بررسي قرار خواهند گرفت. درصورتيكه مطالب اين قسمت براي شما دشوار و يا گنگ بود نگران و يا ناراحت نشويد چرا كه فعلاً براي يادگيري اين مطالب آنهم بدون مقدمه اندكي زود است، بيشتر هدف من از اين بخش آشنا شدن شما با كاربردهاي پيشرفتهتر رخدادها در برنامهنويسي بوده است.
كلاسهاي GUI مربوط به .Net Framework بطور گستردهاي از رخدادها استفاده مينمايند. در مثالي كه در اينجا مورد بررسي قرار ميدهيم، برنامهاي وجود دارد كه داراي يك فرم به همراه دو دكمه (Button) بر روي آن است. اين دو دكمه بوسيلة دو فيلد از نوع Button ايجاد ميشوند. (Button عضو System.Windows.Forms است). كلاس Button از كلاس Control ارثبري ميكند و داراي رخدادي با نام Click از نوع EventHandler است. به مثال توجه نماييد.
namespace System
{
public delegate void EventHandler(object sender, EventArgs args);
public class EventArgs
{
⋮
}
}
namespace System.Windows.Forms
{
public class Control :
{
public event EventHandler Click;
⋮
}
public class Button : Control
{
⋮
}
}
توجه نماييد كه كد فوق، كد مربوط به namespace مربوط به System است كه نحوة پيادهسازي آنرا نشان ميدهد. همانطور كه ملاحظه مينماييد، درون System، delegate ي با نام EventHandler تعريف شده است. در زير اين namespace، اعلان System.Windows.Forms نيز آورده شده تا نحوة اعلان رخداد Click و ارثبري كلاس Button از كلاس Control نيز مشخص شود.
پس از اينكه بر روي دكمهاي واقع در فرم ويندوز كليك كنيد، Button بطور خودكار رخداد Click را فرا ميخواند. هدايت اين پروسه باعث ميشود تا بتوان به سادگي delegate ي براي كنترل اين رخداد ايجاد نمود. در مثالي كه در زير مشاهده ميكنيد، دكمهاي با نام Okay، متدي بنام Okay_Click و رخدادي جهت اتصال Okay به متد Okay_Click وجود دارد.
class Example : System.Windows.Forms.Form
{
private System.Windows.Forms.Button okay;
⋮
public Example()
{
this.okay = new System.Windows.Forms.Button();
this.okay.Click += new System.EventHandler(this.okay_Click);
⋮
}
private void okay_Click(object sender, System.EventsArgs args)
{
⋮
}
}
همانطور كه مشاهده ميكنيد، كلاس Example از System.Windows.Forms.Form مشتق ميشود، از اينرو تمامي خواص آن را به ارث ميبرد. Okay نيز از نوع Button اعلان شده است. درون سازندة (Constructor) كلاس Example، متد Okay.Click به رخداد افزوده شده و مرجع this.Okay.Click نيز، متد مورد نظر را تعيين نموده است. همانطور كه گفته شد، EventHandler نيز delegate مورد نظر در اين مثال است. درون متد Okay_Click نيز ميتوان كد خاصي را قرار داد تا عمل مورد نظر را انجام دهد. پس از كليك كردن بر روي دكمة Okay، عمل مورد نظري كه درون متد Okay_Click قرار داده شده، اجرا ميشود.
اين كد، شبيه به كدهايي است كه توسط محيطهاي برنامهسازي نظير Visual Studio.Net و يا C#Builder بطور خودكار توليد ميشوند و تنها كافيست تا شما كد مربوط به Okay_Click را درون آن وارد نماييد.
رخدادهايي كه توسط كلاسهاي GUI توليد ميشوند همواره از يك الگوي خاص پيروي ميكنند. اين رخدادها همواره از نوع delegate ي هستند كه مقدار بازگشتي ندارد (void) و داراي دو آرگومان است. آرگومان اول هميشه فرستندة رخداد و آرگومان دوم هميشه آرگومان EventArgs يا كلاس مشتق شده از EventArgs است.
آرگومان sender به شما اين امكان را ميدهد تا از يك delegate براي چندين رخداد استفاده نماييد. (بعنوان مثال براي چندين دكمه.)
نكاتي چند درباره delegate ها و event ها
Delegate ها بطور ضمني از System.Delegate ارثبري ميكنند. Delegate حاوي متدها، property ها و عملگرهايي است كه ميتوان آنها را بعنوان پارامتر به متدهاي ديگر ارسال نمود. همچنين به دليل اينكه System.Delegate بخشي از .Net Framework است، از اينرو delegate هاي ايجاد شده در C# را ميتوان در زبانهاي ديگري نظير Visual Basic.Net نيز استفاده نمود.
هنگام اعلان پارامترها براي delegate، حتماً بايد براي آنها نام در نظر بگيريد و فقط به مشخص كردن نوع پارامترها بسنده نكنيد.
رخدادها عناصر بسيار مفيد و پر استفادهاي هستند كه با بكارگيري delegate ها بسيار قدرتمند ظاهر ميشوند. بدست آوردن مهارت در ايجاد و استفاده از آنها نياز به تمرين و تفكر بسيار دارد.
مطالب اين درس در اينجا به پايان رسيد. اميدوارم مورد قبول شما بوده باشد. خواهشمندام نظرات وپيشنهادات ارزندة خود را براي من ايميل كنيد تا از آنها در جهت بهبود مطالب سايت استفاده گردد.
زندگي صحنه يکتاي هنرمندي ماست هرکسي نغمه خود خواند و از صحنه رود
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
لطفا سوالات فني را فقط در خود انجمن مطرح بفرماييد، به اين سوالات در PM پاسخ داده نخواهد شد
- پست: 15889
- تاریخ عضویت: جمعه ۷ بهمن ۱۳۸۴, ۷:۵۱ ب.ظ
- سپاسهای ارسالی: 72671 بار
- سپاسهای دریافتی: 31672 بار
- تماس:
درس پانزدهم - برخورد با استثناها (Exception Handling)
در اين درس با چگونگی برخورد با استثناها (يا خطاهاي غير قابل پيشبيني) در زبان برنامهسازي C# آشنا ميشويم. اهداف ما در اين درس بشرح زير ميباشد :
1) درک و فهم صحيح يک استثناء يا Exception
2) پيادهسازي يک روتين براي برخورد با استثناها بوسيله بلوک try/catch
3) آزادسازي منابع تخصيص داده شده به يک برنامه در يک بلوک finally
استثناها، در حقيقت خطاهاي غير منتظره در برنامههاي ما هستند. اکثراً، ميتوان و بايد روشهايي را جهت برخورد با خطاهای موجود در برنامه در نظر گرفت و آنها را پيادهسازی کرد. بعنوان مثال، بررسي و تاييد دادههای ورودی کاربران، بررسی اشياء تهی يا Null و يا بررسی نوع بازگشتی متد ها، ميتوانند از جمله مواردی باشند که بايد مورد بررسی قرار گيرند. اين خطاها، خطاهايی معمول و رايجی هستند که اکثر برنامهنويسان از آنها مطلع بوده و راههايی را برای بررسی آنها در نظر ميگيرند تا از وقوع آنها جلوگيری نمايند.
اما زمانهايي وجود دارند که از اتفاق افتادن يک خطا در برنامه بی اطلاع هستيد و انتظار وقوع خطا در برنامه را نداريد. بعنوان مثال، هرگز نميتوان وقوع يک خطای I/O را پيشبينی نمود و يا کمبود حافظه برای اجرای برنامه و از کار افتادن برنامه به اين دليل. اين موارد بسيار غير منتظره و ناخواسته هستند، اما در صورت وقوع بهتر است بتوان راهی برای مقابله و برخورد با آنها پيدا کرده و با آنها برخورد نمود. در اين جاست که مسئله برخورد با استثناها (Exception Handling) مطرح ميشود.
هنگاميکه استثنايی رخ ميدهد، در اصطلاح ميگوئيم که اين استثناء، thrown شده است. در حقيقت thrown، شیءای است مشتق شده از کلاس System.Exception که اطلاعاتی در مورد خطا يا استثناء رخ داده را نشان ميدهد. در قسمتهای مختلف اين درس با روش مقابله با استثناها با استفاده از بلوک های try/catch آشنا خواهيد شد.
کلاس System.Exception حاوی تعداد بسيار زيادی متد و property است که اطلاعات مهمی در مورد استثناء و خطای رخ داده را در اختيار ما قرار ميدهد. برای مثال، Message يکی از property های موجود در اين کلاس است که اطلاعاتی درباره نوع استثناء رخ داده در اختيار ما قرار ميدهد. StackTrace نيز، اطلاعاتی در مورد Stack (پشته) و محل وقوع خطا در Stack در اختيار ما قرار خواهد داد.
تشخيص چنين استثناهايی، دقيقاً با روتينهای نوشته شده توسط برنامهنويس در ارتباط هستند و بستگی کامل به الگوريتمی دارد که وی برای چنين شرايطی در نظر گرفته است. برای مثال، در صورتيکه با استفاده از متد System.IO.File.OpenRead()، اقدام به باز کردن فايلی نماييم، احتمال وقوع (Thrown) يکی از استثناهای زير وجود دارد :
SecurityException
ArgumentException
ArgumentNullException
PathTooLongException
DirectoryNotFoundException
UnauthorizedAccessException
FileNotFoundException
NotSupportedException
با نگاهی بر مستندات .Net Framework SDK، به سادگی ميتوان از خطاها و استثناهايی که ممکن است يک متد ايجاد کند، مطلع شد. تنها کافيست به قسمت Reference/Class Library رفته و مستندات مربوط به Namespace/Class/Method را مطالعه نماييد. در اين مستندات هر خطا دارای لينکی به کلاس تعريف کننده خود است که با استفاده از آن ميتوان متوجه شد که اين استثناء به چه موضوعی مربوط است. پس از اينکه از امکان وقوع خطايي در قسمتی از برنامه مطلع شديد، لازم است تا با استفاده از مکانيزمی صحيح به مقابله با آن بپردازيد.
هنگاميکه يک استثناء در اصطلاح thrown ميشود (يا اتفاق ميافتد) بايد بتوان به طريقی با آن مقابله نمود. با استفاده از بلوکهای try/catch ميتوان چنين عملی را انجام داد. پيادهسازی اين بلوکها بدين شکل هستند که، کدی را که احتمال توليد استثناء در آن وجود دارد را در بلوک try، و کد مربوط به مقابله با اين استثناء رخ داده را در بلوک catch قرار ميدهيم. در مثال 1-15 چگونگی پيادهسازی يک بلوک try/catch نشان داده شده است. بدليل اينکه متد OpenRead() احتمال ايجاد يکی از استثناهای گفته شده در بالا را دارد، آنرا در بلوک try قرار داده ايم. در صورتيکه اين خطا رخ دهد، با آن در بلوک catch مقابله خواهيم کرد. در مثال 1-15 در صورت بروز استثناء، پيغامی در مورد استثناء رخ داده و اطلاعاتی در مورد محل وقوع آن در Stack برای کاربر بر روی کنسول نمايش داده ميشود.
نکته : توجه نماييد که کليه مثالهای موجود در اين درس به طور تعمدی دارای خطاهايی هستند تا شما با نحوه مقابله با استثناها آشنا شويد.
using System;
using System.IO;
class TryCatchDemo
{
static void Main(string[] args)
{
try
{
File.OpenRead("NonExistentFile");
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
هر چند کد موجود در مثال 1-15 تنها داری يک بلوک catch است، اما تمامی استثناهايي که ممکن است رخ دهند را نشان داده و مورد بررسی قرار ميدهد زيرا از نوع کلاس پايه استثناء، يعنی Exception تعريف شده است. در کنترل و مقابله با استثناها، بايد استثناهای خاص را زودتر از استثناهای کلی مورد بررسی قرار داد. کد زير نحوه استفاده از چند بلوک catch را نشان ميدهد :
catch(FileNotFoundException fnfex)
{
Console.WriteLine(fnfex.ToString());
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
در اين کد، در صورتيکه فايل مورد نظر وجود نداشته باشد، FileNotFoundException رخ داده و توسط اولين بلوک catch مورد بررسی قرار ميگيرد. اما در صورتيکه PathTooLongException رخ دهد، توسط دومين بلوک catch بررسی خواهد شد. علت آنست که برای PathTooLongException بلوک catch ای در نظر گرفته نشده است و تنها گزينه موجود جهت بررسی اين استثناء بلوک کلی Exception است. نکته ای که در اينجا بايد بدان توجه نمود آنست که هرچه بلوکهای catch مورد استفاده خاص تر و جزئی تر باشند، پيغامها و اطلاعات مفيدتری در مورد خطا ميتوان بدست آورد.
استثناهايی که مورد بررسی قرار نگيرند، در بالای Stack نگهداری می شوند تا زمانيکه بلوک try/catch مناسبی مربوط به آنها يافت شود. در صورتيکه برای استثناء رخ داده بلوک try/catch در نظر گرفته نشده باشد، برنامه متوقف شده و پيغام خطايي ظاهر ميگردد. اين چنين حالتی بسيار نا مناسب بوده و کاربران را دچار آشفتگی خواهد کرد. استفاده از روشهای مقابله با استثناها در برنامه، روشی مناسب و رايج است و باعث قدرتمند تر شدن برنامه ميشود.
يکی از حالتهای بسيار خطرناک و نامناسب در زمان وقوع استثناها، هنگامی است که استثناء يا خطای رخ داده باعث از کار افتادن برنامه شود ولی منابع تخصيص داده شده به آن برنامه آزاد نشده باشند. هر چند بلوک catch برای برخورد با استثناها مناسب است ولی در مورد گفته شده نمی تواند کمکی به حل مشکل نمايد. برای چنين شرايطی که نياز به آزادسازی منابع تخصيص داده شده به يک برنامه داريم، از بلوک finally استفاده ميکنيم.
کد نشان داده شده در مثال 2-15، به خوبی روش استفاده از بلوک finally را نشان ميدهد. همانطور که حتماً ميدانيد، رشته های فايلی پس از اينکه کار با آنها به اتمام ميرسد بايد بسته شوند، در غير اينصورت هيچ برنامه ديگری قادر به استفاده از آنها نخواهد بود. در اين حالت، رشته فايلی، منبعی است که ميخواهيم پس از باز شدن و اتمام کار، بسته شده و به سيستم باز گردد. در مثال 2-15، outStream با موفقيت باز ميشود، بدين معنا که برنامه handle ای به يک فايل باز شده در اختيار دارد. اما زمانيکه ميخواهيم inStraem را باز کنيم، استثناء FileNotFound رخ داده و باعث ميشود که کنترل برنامه سريعاً به بلوک catch منتقل گردد.
در بلوک catch ميتوانيم فايل outStream را ببنديم. اما برنامه تنها زمانی به بلوک catch وارد ميشود که استثنايي رخ دهد. پس اگر هيچ استثنائی رخ نداده و برنامه به درستی عمل نمايد، فايل باز شده outStream هرگز بسته نشده و يکی از منابع سيستم به آن بازگردانده نميشود. بنابراين بايد برای بستن اين فايل نيز فکری کرد. اين کاری است که در بلوک finally رخ می دهد. بدين معنا که در هر حالت، چه برنامه با استثنائی روبرو شود و چه نشود، قبل از خروج از برنامه فايل باز شده، بسته خواهد شد. در حقيقت ميتوان گفت بلوک finally، بلوکی است که تضمين مينمايد در هر شرايطی اجرا خواهد شد. پس برای حصول اطمينان از اينکه منابع مورد استفاده برنامه پس از خروج برنامه، به سيستم باز گردانده ميشوند، ميتوان از اين بلوک استفاده کرد.
using System;
using System.IO;
class FinallyDemo
{
static void Main(string[] args)
{
FileStream outStream = null;
FileStream inStream = null;
try
{
outStream = File.OpenWrite("DestinationFile.txt");
inStream = File.OpenRead("BogusInputFile.txt");
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (outStream != null)
{
outStream.Close();
Console.WriteLine("outStream closed.");
}
if (inStream != null)
{
inStream.Close();
Console.WriteLine("inStream closed.");
}
}
}
}
استفاده از بلوک finally الزامی نيست، اما روشی مناسب برای بالا بردن کارآيي برنامه است. ممکن است سوالی در اينجا مطرح شود : در صورتيکه پس از بلوک catch و بدون استفاده از بلوک finally، فايل باز شده را ببنديم، باز هم منبع تخصيص داده شده به برنامه آزاد می شود. پس چه دليلی برای استفاده از بلوک finally وجود دارد؟ در پاسخ به اين سوال بايد گفت، در شرايط نرمال که تمامی برنامه بطور طبيعی اجرا ميشود و اتفاق خاصی رخ نميدهد، می توان گفت که دستورات بعد از بلوک catch اجرا شده و منبع تخصيص داده شده به سيستم آزاد می شود. اما برای بررسی هميشه بايد بدترين حالت را در نظر گرفت. فرض کنيد درون خود بلوک catch استثنائی رخ دهد که شما آنرا پيشبينی نکردهايد و يا اين استثناء باعت متوقف شدن برنامه شود، در چنين حالتی کدهای موجود بعد از بلوک catch هرگر اجرا نخواهند شد و فايل همچنان باز ميماند. اما با استفاده از بلوک finally ميتوان مطمئن بود که کد موجود در اين بلوک حتماً اجرا شده و منبع تخصيص داده شده به برنامه آزاد ميگردد.
در اينجا به پايان درس پانزدهم رسيديم. هم اکنون می بايست درک صحيحی از استثناء بدست آورده باشيد. همچنين ميتوانيد به سادگی الگوريتمهايي جهت بررسی استثناها بوسيله بلوکهای try/catch پيادهسازی نماييد. بعلاوه ميتوانيد با ساتفاده از بلوک finally مطمئن باشيد که که منابع تخصيص داده شده به برنامه، به سيستم باز خواهند گشت چراکه اين بلوک حتما اجرا ميشود و ميتوان کدهای مهمی را که ميخواهيم تحت هر شرايطی اجرا شوند را درون آن قرار داد.
در اين درس با چگونگی برخورد با استثناها (يا خطاهاي غير قابل پيشبيني) در زبان برنامهسازي C# آشنا ميشويم. اهداف ما در اين درس بشرح زير ميباشد :
1) درک و فهم صحيح يک استثناء يا Exception
2) پيادهسازي يک روتين براي برخورد با استثناها بوسيله بلوک try/catch
3) آزادسازي منابع تخصيص داده شده به يک برنامه در يک بلوک finally
استثناها، در حقيقت خطاهاي غير منتظره در برنامههاي ما هستند. اکثراً، ميتوان و بايد روشهايي را جهت برخورد با خطاهای موجود در برنامه در نظر گرفت و آنها را پيادهسازی کرد. بعنوان مثال، بررسي و تاييد دادههای ورودی کاربران، بررسی اشياء تهی يا Null و يا بررسی نوع بازگشتی متد ها، ميتوانند از جمله مواردی باشند که بايد مورد بررسی قرار گيرند. اين خطاها، خطاهايی معمول و رايجی هستند که اکثر برنامهنويسان از آنها مطلع بوده و راههايی را برای بررسی آنها در نظر ميگيرند تا از وقوع آنها جلوگيری نمايند.
اما زمانهايي وجود دارند که از اتفاق افتادن يک خطا در برنامه بی اطلاع هستيد و انتظار وقوع خطا در برنامه را نداريد. بعنوان مثال، هرگز نميتوان وقوع يک خطای I/O را پيشبينی نمود و يا کمبود حافظه برای اجرای برنامه و از کار افتادن برنامه به اين دليل. اين موارد بسيار غير منتظره و ناخواسته هستند، اما در صورت وقوع بهتر است بتوان راهی برای مقابله و برخورد با آنها پيدا کرده و با آنها برخورد نمود. در اين جاست که مسئله برخورد با استثناها (Exception Handling) مطرح ميشود.
هنگاميکه استثنايی رخ ميدهد، در اصطلاح ميگوئيم که اين استثناء، thrown شده است. در حقيقت thrown، شیءای است مشتق شده از کلاس System.Exception که اطلاعاتی در مورد خطا يا استثناء رخ داده را نشان ميدهد. در قسمتهای مختلف اين درس با روش مقابله با استثناها با استفاده از بلوک های try/catch آشنا خواهيد شد.
کلاس System.Exception حاوی تعداد بسيار زيادی متد و property است که اطلاعات مهمی در مورد استثناء و خطای رخ داده را در اختيار ما قرار ميدهد. برای مثال، Message يکی از property های موجود در اين کلاس است که اطلاعاتی درباره نوع استثناء رخ داده در اختيار ما قرار ميدهد. StackTrace نيز، اطلاعاتی در مورد Stack (پشته) و محل وقوع خطا در Stack در اختيار ما قرار خواهد داد.
تشخيص چنين استثناهايی، دقيقاً با روتينهای نوشته شده توسط برنامهنويس در ارتباط هستند و بستگی کامل به الگوريتمی دارد که وی برای چنين شرايطی در نظر گرفته است. برای مثال، در صورتيکه با استفاده از متد System.IO.File.OpenRead()، اقدام به باز کردن فايلی نماييم، احتمال وقوع (Thrown) يکی از استثناهای زير وجود دارد :
SecurityException
ArgumentException
ArgumentNullException
PathTooLongException
DirectoryNotFoundException
UnauthorizedAccessException
FileNotFoundException
NotSupportedException
با نگاهی بر مستندات .Net Framework SDK، به سادگی ميتوان از خطاها و استثناهايی که ممکن است يک متد ايجاد کند، مطلع شد. تنها کافيست به قسمت Reference/Class Library رفته و مستندات مربوط به Namespace/Class/Method را مطالعه نماييد. در اين مستندات هر خطا دارای لينکی به کلاس تعريف کننده خود است که با استفاده از آن ميتوان متوجه شد که اين استثناء به چه موضوعی مربوط است. پس از اينکه از امکان وقوع خطايي در قسمتی از برنامه مطلع شديد، لازم است تا با استفاده از مکانيزمی صحيح به مقابله با آن بپردازيد.
هنگاميکه يک استثناء در اصطلاح thrown ميشود (يا اتفاق ميافتد) بايد بتوان به طريقی با آن مقابله نمود. با استفاده از بلوکهای try/catch ميتوان چنين عملی را انجام داد. پيادهسازی اين بلوکها بدين شکل هستند که، کدی را که احتمال توليد استثناء در آن وجود دارد را در بلوک try، و کد مربوط به مقابله با اين استثناء رخ داده را در بلوک catch قرار ميدهيم. در مثال 1-15 چگونگی پيادهسازی يک بلوک try/catch نشان داده شده است. بدليل اينکه متد OpenRead() احتمال ايجاد يکی از استثناهای گفته شده در بالا را دارد، آنرا در بلوک try قرار داده ايم. در صورتيکه اين خطا رخ دهد، با آن در بلوک catch مقابله خواهيم کرد. در مثال 1-15 در صورت بروز استثناء، پيغامی در مورد استثناء رخ داده و اطلاعاتی در مورد محل وقوع آن در Stack برای کاربر بر روی کنسول نمايش داده ميشود.
نکته : توجه نماييد که کليه مثالهای موجود در اين درس به طور تعمدی دارای خطاهايی هستند تا شما با نحوه مقابله با استثناها آشنا شويد.
using System;
using System.IO;
class TryCatchDemo
{
static void Main(string[] args)
{
try
{
File.OpenRead("NonExistentFile");
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
هر چند کد موجود در مثال 1-15 تنها داری يک بلوک catch است، اما تمامی استثناهايي که ممکن است رخ دهند را نشان داده و مورد بررسی قرار ميدهد زيرا از نوع کلاس پايه استثناء، يعنی Exception تعريف شده است. در کنترل و مقابله با استثناها، بايد استثناهای خاص را زودتر از استثناهای کلی مورد بررسی قرار داد. کد زير نحوه استفاده از چند بلوک catch را نشان ميدهد :
catch(FileNotFoundException fnfex)
{
Console.WriteLine(fnfex.ToString());
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
در اين کد، در صورتيکه فايل مورد نظر وجود نداشته باشد، FileNotFoundException رخ داده و توسط اولين بلوک catch مورد بررسی قرار ميگيرد. اما در صورتيکه PathTooLongException رخ دهد، توسط دومين بلوک catch بررسی خواهد شد. علت آنست که برای PathTooLongException بلوک catch ای در نظر گرفته نشده است و تنها گزينه موجود جهت بررسی اين استثناء بلوک کلی Exception است. نکته ای که در اينجا بايد بدان توجه نمود آنست که هرچه بلوکهای catch مورد استفاده خاص تر و جزئی تر باشند، پيغامها و اطلاعات مفيدتری در مورد خطا ميتوان بدست آورد.
استثناهايی که مورد بررسی قرار نگيرند، در بالای Stack نگهداری می شوند تا زمانيکه بلوک try/catch مناسبی مربوط به آنها يافت شود. در صورتيکه برای استثناء رخ داده بلوک try/catch در نظر گرفته نشده باشد، برنامه متوقف شده و پيغام خطايي ظاهر ميگردد. اين چنين حالتی بسيار نا مناسب بوده و کاربران را دچار آشفتگی خواهد کرد. استفاده از روشهای مقابله با استثناها در برنامه، روشی مناسب و رايج است و باعث قدرتمند تر شدن برنامه ميشود.
يکی از حالتهای بسيار خطرناک و نامناسب در زمان وقوع استثناها، هنگامی است که استثناء يا خطای رخ داده باعث از کار افتادن برنامه شود ولی منابع تخصيص داده شده به آن برنامه آزاد نشده باشند. هر چند بلوک catch برای برخورد با استثناها مناسب است ولی در مورد گفته شده نمی تواند کمکی به حل مشکل نمايد. برای چنين شرايطی که نياز به آزادسازی منابع تخصيص داده شده به يک برنامه داريم، از بلوک finally استفاده ميکنيم.
کد نشان داده شده در مثال 2-15، به خوبی روش استفاده از بلوک finally را نشان ميدهد. همانطور که حتماً ميدانيد، رشته های فايلی پس از اينکه کار با آنها به اتمام ميرسد بايد بسته شوند، در غير اينصورت هيچ برنامه ديگری قادر به استفاده از آنها نخواهد بود. در اين حالت، رشته فايلی، منبعی است که ميخواهيم پس از باز شدن و اتمام کار، بسته شده و به سيستم باز گردد. در مثال 2-15، outStream با موفقيت باز ميشود، بدين معنا که برنامه handle ای به يک فايل باز شده در اختيار دارد. اما زمانيکه ميخواهيم inStraem را باز کنيم، استثناء FileNotFound رخ داده و باعث ميشود که کنترل برنامه سريعاً به بلوک catch منتقل گردد.
در بلوک catch ميتوانيم فايل outStream را ببنديم. اما برنامه تنها زمانی به بلوک catch وارد ميشود که استثنايي رخ دهد. پس اگر هيچ استثنائی رخ نداده و برنامه به درستی عمل نمايد، فايل باز شده outStream هرگز بسته نشده و يکی از منابع سيستم به آن بازگردانده نميشود. بنابراين بايد برای بستن اين فايل نيز فکری کرد. اين کاری است که در بلوک finally رخ می دهد. بدين معنا که در هر حالت، چه برنامه با استثنائی روبرو شود و چه نشود، قبل از خروج از برنامه فايل باز شده، بسته خواهد شد. در حقيقت ميتوان گفت بلوک finally، بلوکی است که تضمين مينمايد در هر شرايطی اجرا خواهد شد. پس برای حصول اطمينان از اينکه منابع مورد استفاده برنامه پس از خروج برنامه، به سيستم باز گردانده ميشوند، ميتوان از اين بلوک استفاده کرد.
using System;
using System.IO;
class FinallyDemo
{
static void Main(string[] args)
{
FileStream outStream = null;
FileStream inStream = null;
try
{
outStream = File.OpenWrite("DestinationFile.txt");
inStream = File.OpenRead("BogusInputFile.txt");
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (outStream != null)
{
outStream.Close();
Console.WriteLine("outStream closed.");
}
if (inStream != null)
{
inStream.Close();
Console.WriteLine("inStream closed.");
}
}
}
}
استفاده از بلوک finally الزامی نيست، اما روشی مناسب برای بالا بردن کارآيي برنامه است. ممکن است سوالی در اينجا مطرح شود : در صورتيکه پس از بلوک catch و بدون استفاده از بلوک finally، فايل باز شده را ببنديم، باز هم منبع تخصيص داده شده به برنامه آزاد می شود. پس چه دليلی برای استفاده از بلوک finally وجود دارد؟ در پاسخ به اين سوال بايد گفت، در شرايط نرمال که تمامی برنامه بطور طبيعی اجرا ميشود و اتفاق خاصی رخ نميدهد، می توان گفت که دستورات بعد از بلوک catch اجرا شده و منبع تخصيص داده شده به سيستم آزاد می شود. اما برای بررسی هميشه بايد بدترين حالت را در نظر گرفت. فرض کنيد درون خود بلوک catch استثنائی رخ دهد که شما آنرا پيشبينی نکردهايد و يا اين استثناء باعت متوقف شدن برنامه شود، در چنين حالتی کدهای موجود بعد از بلوک catch هرگر اجرا نخواهند شد و فايل همچنان باز ميماند. اما با استفاده از بلوک finally ميتوان مطمئن بود که کد موجود در اين بلوک حتماً اجرا شده و منبع تخصيص داده شده به برنامه آزاد ميگردد.
در اينجا به پايان درس پانزدهم رسيديم. هم اکنون می بايست درک صحيحی از استثناء بدست آورده باشيد. همچنين ميتوانيد به سادگی الگوريتمهايي جهت بررسی استثناها بوسيله بلوکهای try/catch پيادهسازی نماييد. بعلاوه ميتوانيد با ساتفاده از بلوک finally مطمئن باشيد که که منابع تخصيص داده شده به برنامه، به سيستم باز خواهند گشت چراکه اين بلوک حتما اجرا ميشود و ميتوان کدهای مهمی را که ميخواهيم تحت هر شرايطی اجرا شوند را درون آن قرار داد.
زندگي صحنه يکتاي هنرمندي ماست هرکسي نغمه خود خواند و از صحنه رود
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
لطفا سوالات فني را فقط در خود انجمن مطرح بفرماييد، به اين سوالات در PM پاسخ داده نخواهد شد
- پست: 15889
- تاریخ عضویت: جمعه ۷ بهمن ۱۳۸۴, ۷:۵۱ ب.ظ
- سپاسهای ارسالی: 72671 بار
- سپاسهای دریافتی: 31672 بار
- تماس:
درس شانزدهم – استفاده از صفتها در 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 در اين صفت، پارامتر اين صفت بعنوان پيغام نمايش داده ميشود.
تعريف و يا کنترل موارد استفاده از يک صفت
AttributeUsage يکی از کلاسهای از پيش تعريف شده در زبان است که با استفاده از آن ميتوانيم موارد استفاده از صفتی را که توليد کردهايم را کنترل کنيم.
اين کلاس دارای سه property مختلف است که ميتوان آنها را به هنگام استفاده صفت شخصی تنظيم نمود و مورد استفاده قرار داد.
ValidOn
با استفاده از اين property ميتوانيم مشخص کنيم که صفت توليد شده توسط ما، بر روی کدام يک از عناصر برنامه قابل اعمال هستند. اطلاعات اين عناصر از AttributeTarget گرفته ميشود و ميتوان عناصر مختلف را بوسيله OR بيتی با يکديگر ترکيب نمود.
AllowMultiple
با استفاده از اين property ميتوان مشخص کرد که آيا ميتوان از اين صفت بيش از يکبار بر روی يک عنصر برنامه استفاده کرد يا نه.
Inherited
با استفاده از اين property ميتوان قوانين ارثبری اين صفت را کنترل نمود. با استفاده از اين property ميتوان مشخص کرد که آيا کلاسی که از کلاسی که صفت بر روی آن اعمال شده، ارث بری ميکند نيز، صفت بر رويش اعمال ميشود يا نه و يا به عبارتی صفت در کلاس مشتق شده نيز مورد ارثبری قرار ميگيرد يا نه.
حال با استفاده از موارد گفته شده در بالا، ميخواهيم اين مطالب را بر روی صفتی که خودمان توليد کرديم اعمال نماييم. مثال 7-16 را بررسی نماييد.
مثال 7-16
using System;
[AttributeUsage(AttributeTargets.Class), AllowMultiple = false, Inherited = false ]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
در ابتدا به AttributeTargets.Class توجه نماييد. اين مشخص ميکند که صفت Help تنها بر روی کلاسها قابل اعمال است و در صورتيکه از آن بر روی عنصری به غير از کلاس استفاده نماييم خطايي رخ خواهد داد. بنابراين کد زير، خطايي توليد خواهد کرد :
[Help("this is a do-nothing class")]
public class AnyClass
{
[Help("this is a do-nothing method")] //error
public void AnyMethod()
{
}
}
و کد خطای توليد شده بشکل زير خواهد بود :
AnyClass.cs: Attribute 'Help' is not valid on this declaration type.
It is valid on 'class' declarations only.
توجه کنيد که با استفاده از AttributeTargets.All به صفت Help اين امکان را ميدهيم تا بر روی تمامی عناصر موجود اعمال شود. ليست کامل عناصر مجاز نيز بشرح زير است :
Assembly,
Module,
Class,
Struct,
Enum,
Constructor,
Method,
Property,
Field,
Event,
Interface,
Parameter,
Delegate,
All = Assembly , Module , Class , Struct , Enum , Constructor , Method , Property , Field , Event , Interface , Parameter , Delegate,
ClassMembers = Class , Struct , Enum , Constructor , Method , Property , Field , Event , Delegate , Interface
حال به AllowMultiple = false توجه نماييد. با استفاده از اين کد، به صفت Help اجازه ميدهيم تا تنها يکبار بر روی عنصری از برنامه اعمال شود. پس کد زير توليد خطا مينمايد :
[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
[Help("this is a do-nothing method")] //error
public void AnyMethod()
{
}
}
و کد خطای توليد شده نيز بصورت زير است :
AnyClass.cs: Duplicate 'Help' attribute
در نهايت نيز به بررسی Inherited ميپردازيم. با استفاده از اين ويژگی، معين ميکنيم درصورتيکه کلاس ديگری بخواهد از روی کلاسی که صفت بر روی آن اعمال شده ارثبری نمايد، آيا اين صفت بر روی آن کلاس نيز اعمال شود يا نه. در صورتيکه مقدار اين ويژگی برابر با True باشد، کلاس مشتق شده نيز از صفت ارثبری مينمايد. برای يک مثال ميتوانيم حالت زير را در نظر بگيريم :
[Help("BaseClass")]
public class Base
{
}
public class Derive : Base
{
}
تمامی حالتهای مختلف ترکيب اين سه ويژگی بصورت زير است :
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]
استفاده از پارامترهای Positional و Named در صفتهای شخصی
همانطور که در قبل نيز اشاره شد، پارامترهای Positional پارامترهای سازنده صفت هستند و در هر بار استفاده از صفت بايد لحاظ شوند. حال برای بررسی ميخواهيم پارامترهايي به صفت Help خود اضافه نماييم.
مثال 8-16
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
this.verion = "No Version is defined for this class";
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
protected String version;
public String Version
{
get
{
return this.version;
}
//if we ever want our attribute user to set this property,
//we must specify set method for it
set
{
this.verion = value;
}
}
}
[Help("This is Class1")]
public class Class1
{
}
[Help("This is Class2", Version = "1.0")]
public class Class2
{
}
[Help("This is Class3", Version = "2.0", Description = "This is do-nothing class")]
public class Class3
{
}
پس از اينکه اين صفت را بر روی کلاس Class1 اعمال کرديم و بخواهيم آنرا کامپايل کنيم با پيغام زير روبرو ميشويم :
Help.Description : This is Class1
Help.Version :No Version is defined for this class
چون در اينجا هيچ مقداری برای Version در نظر نگرفتهايم، با اين پيام مواجه شدهايم.
حال نتيجه اعمال اين صفت را بر روی کلاس دوم بررسی ميکنيم.
Help.Description : This is Class2
Help.Version : 1.0
برای پارامترهای اختياری معمولا از دو سازنده استفاده نميشود و در عوض از پارامترهای Named استفاده ميگردد. نکتهای که بايد به آن توجه کنيد آنست که برای پارامترهای Named حتما بايد در تعريف property، از متد set نيز استفاده نماييد در غير اينصورت با پيغام خطای زير روبرو ميشويد :
'Version' : Named attribute argument can't be a read only property
بنابراين درصورتيکه اين صفت را بر روی کلاس سوم نيز اعمال کنيم با پيغام خطای مشابهی روبرو خواهيم شد. اگر در کلاس Help تغييری کوچکی ایيجاد کنيم و به Description نيز متد set را بيفزاييم، با خطا مواجه نخواهيم شد.
Help.Description : This is do-nothing class
Help.Version : 2.0
اتفاقی که در اينجا رخ ميدهد آنست که در ابتدا سازنده (Constructor) اين صفت به همراه پارامترهای Positional آن فراخوانده ميشوند و سپس متد set برای هر يک از پارامترهای Named فراخوانده ميشود .
انواع (type) معتبر برای پارامترهای صفت
انواع معتبر برای پارامترهای صفت بشرح زير ميباشند :
bool,byte,char,double,float,int,long,short,string,System.Type ,object
همچنين ميتوان از enum و يا آرايهای تک بعدی، که عناصر آن يکی از انواع فوق باشد، نيز استفاده نمود.
استفاده از صفتها در زمان اجرا
تا كنون با طريقه ساخت صفتها و چگونگي استفاده و اعمال آنها بر عناصر مختلف برنامه آشنا شديم. حال نوبت به آن رسيده است تا ببينيم چگونه ميتوان از صفتها در زمان اجرا استفاده نمود. براي جستجوي (query) يك برنامه درباره صفت موجود در آن، به Reflection نيازمنديم. Reflection قابليت بدست آوردن اطلاعات مربوط به انواع (Types) مختلف در زمان اجراي برنامه است. با استفاده از توابع Reflection موجود در .Net Framework ميتوانيم با جستجو و پيمايش Metadate مربوط به يك اسمبلي، ليست كاملي از كلاسها، انواع و متدهايي را كه براي آن اسمبلي خاص تعريف شدهاند را، بدست آوريم. به مثال 9-16 در اين باره توجه نماييد.
مثال 9-16 : استفاده از Reflection
using System;
using System.Reflection;
using System.Diagnostics;
//attaching Help attribute to entire assembly
[assembly : Help("This Assembly demonstrates custom attributes creation and their run-time query.")]
//our custom attribute class
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
//
// TODO: Add constructor logic here
this.description = Description_in;
//
}
protected String description;
public String Description
{
get
{
return this.deescription;
}
}
}
//attaching Help attribute to our AnyClass
[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching Help attribute to our AnyMethod
[Help("This is a do-nothing Method.")]
public void AnyMethod()
{
}
//attaching Help attribute to our AnyInt Field
[Help("This is any Integer.")]
public int AnyInt;
}
class QueryApp
{
public static void Main()
{
}
}
مبحث صفتها بسيار گسترده است و ميتوان ساعتها در مورد صفتهای مختلف بحث نمود. اما آنچه مسلم است تمرين مستمر و پيگيری برنامه نويس در يافتن مواردی که ميتوان با استفاده از صفتها، برنامهای پوياتر ايجاد نمود، مهمترين عامل در فهم و درک کامل مبحث خواهد بود. درباره Reflection نيز در آينده در يان سايت مفصلا توضيح خواهم داد.
خلاصه :
در اين درس با صفتها آشنا شديد. ياد گرفتيد كه چگونه از صفتهاي موجود در .Net Framework استفاده كرده و همچنين چگونه صفتهاي شخصي و دلخواه خود را توليد نماييد. همچنين با پارامترهاي صفتها و انواع آنها و نيز، عناصري كه صفتها بر روي آنها اعمال ميشوند آشنا شديد. در انتهاي درس نيز به مختصر درباره Reflection و چگونگي استفاده از صفتها در زمان اجرا صحبت كرديم. اميدوارم مفيد واقع شده باشد.
در اين درس با نحوه استفاده از صفتها در زبان 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 در اين صفت، پارامتر اين صفت بعنوان پيغام نمايش داده ميشود.
تعريف و يا کنترل موارد استفاده از يک صفت
AttributeUsage يکی از کلاسهای از پيش تعريف شده در زبان است که با استفاده از آن ميتوانيم موارد استفاده از صفتی را که توليد کردهايم را کنترل کنيم.
اين کلاس دارای سه property مختلف است که ميتوان آنها را به هنگام استفاده صفت شخصی تنظيم نمود و مورد استفاده قرار داد.
ValidOn
با استفاده از اين property ميتوانيم مشخص کنيم که صفت توليد شده توسط ما، بر روی کدام يک از عناصر برنامه قابل اعمال هستند. اطلاعات اين عناصر از AttributeTarget گرفته ميشود و ميتوان عناصر مختلف را بوسيله OR بيتی با يکديگر ترکيب نمود.
AllowMultiple
با استفاده از اين property ميتوان مشخص کرد که آيا ميتوان از اين صفت بيش از يکبار بر روی يک عنصر برنامه استفاده کرد يا نه.
Inherited
با استفاده از اين property ميتوان قوانين ارثبری اين صفت را کنترل نمود. با استفاده از اين property ميتوان مشخص کرد که آيا کلاسی که از کلاسی که صفت بر روی آن اعمال شده، ارث بری ميکند نيز، صفت بر رويش اعمال ميشود يا نه و يا به عبارتی صفت در کلاس مشتق شده نيز مورد ارثبری قرار ميگيرد يا نه.
حال با استفاده از موارد گفته شده در بالا، ميخواهيم اين مطالب را بر روی صفتی که خودمان توليد کرديم اعمال نماييم. مثال 7-16 را بررسی نماييد.
مثال 7-16
using System;
[AttributeUsage(AttributeTargets.Class), AllowMultiple = false, Inherited = false ]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
در ابتدا به AttributeTargets.Class توجه نماييد. اين مشخص ميکند که صفت Help تنها بر روی کلاسها قابل اعمال است و در صورتيکه از آن بر روی عنصری به غير از کلاس استفاده نماييم خطايي رخ خواهد داد. بنابراين کد زير، خطايي توليد خواهد کرد :
[Help("this is a do-nothing class")]
public class AnyClass
{
[Help("this is a do-nothing method")] //error
public void AnyMethod()
{
}
}
و کد خطای توليد شده بشکل زير خواهد بود :
AnyClass.cs: Attribute 'Help' is not valid on this declaration type.
It is valid on 'class' declarations only.
توجه کنيد که با استفاده از AttributeTargets.All به صفت Help اين امکان را ميدهيم تا بر روی تمامی عناصر موجود اعمال شود. ليست کامل عناصر مجاز نيز بشرح زير است :
Assembly,
Module,
Class,
Struct,
Enum,
Constructor,
Method,
Property,
Field,
Event,
Interface,
Parameter,
Delegate,
All = Assembly , Module , Class , Struct , Enum , Constructor , Method , Property , Field , Event , Interface , Parameter , Delegate,
ClassMembers = Class , Struct , Enum , Constructor , Method , Property , Field , Event , Delegate , Interface
حال به AllowMultiple = false توجه نماييد. با استفاده از اين کد، به صفت Help اجازه ميدهيم تا تنها يکبار بر روی عنصری از برنامه اعمال شود. پس کد زير توليد خطا مينمايد :
[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
[Help("this is a do-nothing method")] //error
public void AnyMethod()
{
}
}
و کد خطای توليد شده نيز بصورت زير است :
AnyClass.cs: Duplicate 'Help' attribute
در نهايت نيز به بررسی Inherited ميپردازيم. با استفاده از اين ويژگی، معين ميکنيم درصورتيکه کلاس ديگری بخواهد از روی کلاسی که صفت بر روی آن اعمال شده ارثبری نمايد، آيا اين صفت بر روی آن کلاس نيز اعمال شود يا نه. در صورتيکه مقدار اين ويژگی برابر با True باشد، کلاس مشتق شده نيز از صفت ارثبری مينمايد. برای يک مثال ميتوانيم حالت زير را در نظر بگيريم :
[Help("BaseClass")]
public class Base
{
}
public class Derive : Base
{
}
تمامی حالتهای مختلف ترکيب اين سه ويژگی بصورت زير است :
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]
استفاده از پارامترهای Positional و Named در صفتهای شخصی
همانطور که در قبل نيز اشاره شد، پارامترهای Positional پارامترهای سازنده صفت هستند و در هر بار استفاده از صفت بايد لحاظ شوند. حال برای بررسی ميخواهيم پارامترهايي به صفت Help خود اضافه نماييم.
مثال 8-16
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
this.verion = "No Version is defined for this class";
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
protected String version;
public String Version
{
get
{
return this.version;
}
//if we ever want our attribute user to set this property,
//we must specify set method for it
set
{
this.verion = value;
}
}
}
[Help("This is Class1")]
public class Class1
{
}
[Help("This is Class2", Version = "1.0")]
public class Class2
{
}
[Help("This is Class3", Version = "2.0", Description = "This is do-nothing class")]
public class Class3
{
}
پس از اينکه اين صفت را بر روی کلاس Class1 اعمال کرديم و بخواهيم آنرا کامپايل کنيم با پيغام زير روبرو ميشويم :
Help.Description : This is Class1
Help.Version :No Version is defined for this class
چون در اينجا هيچ مقداری برای Version در نظر نگرفتهايم، با اين پيام مواجه شدهايم.
حال نتيجه اعمال اين صفت را بر روی کلاس دوم بررسی ميکنيم.
Help.Description : This is Class2
Help.Version : 1.0
برای پارامترهای اختياری معمولا از دو سازنده استفاده نميشود و در عوض از پارامترهای Named استفاده ميگردد. نکتهای که بايد به آن توجه کنيد آنست که برای پارامترهای Named حتما بايد در تعريف property، از متد set نيز استفاده نماييد در غير اينصورت با پيغام خطای زير روبرو ميشويد :
'Version' : Named attribute argument can't be a read only property
بنابراين درصورتيکه اين صفت را بر روی کلاس سوم نيز اعمال کنيم با پيغام خطای مشابهی روبرو خواهيم شد. اگر در کلاس Help تغييری کوچکی ایيجاد کنيم و به Description نيز متد set را بيفزاييم، با خطا مواجه نخواهيم شد.
Help.Description : This is do-nothing class
Help.Version : 2.0
اتفاقی که در اينجا رخ ميدهد آنست که در ابتدا سازنده (Constructor) اين صفت به همراه پارامترهای Positional آن فراخوانده ميشوند و سپس متد set برای هر يک از پارامترهای Named فراخوانده ميشود .
انواع (type) معتبر برای پارامترهای صفت
انواع معتبر برای پارامترهای صفت بشرح زير ميباشند :
bool,byte,char,double,float,int,long,short,string,System.Type ,object
همچنين ميتوان از enum و يا آرايهای تک بعدی، که عناصر آن يکی از انواع فوق باشد، نيز استفاده نمود.
استفاده از صفتها در زمان اجرا
تا كنون با طريقه ساخت صفتها و چگونگي استفاده و اعمال آنها بر عناصر مختلف برنامه آشنا شديم. حال نوبت به آن رسيده است تا ببينيم چگونه ميتوان از صفتها در زمان اجرا استفاده نمود. براي جستجوي (query) يك برنامه درباره صفت موجود در آن، به Reflection نيازمنديم. Reflection قابليت بدست آوردن اطلاعات مربوط به انواع (Types) مختلف در زمان اجراي برنامه است. با استفاده از توابع Reflection موجود در .Net Framework ميتوانيم با جستجو و پيمايش Metadate مربوط به يك اسمبلي، ليست كاملي از كلاسها، انواع و متدهايي را كه براي آن اسمبلي خاص تعريف شدهاند را، بدست آوريم. به مثال 9-16 در اين باره توجه نماييد.
مثال 9-16 : استفاده از Reflection
using System;
using System.Reflection;
using System.Diagnostics;
//attaching Help attribute to entire assembly
[assembly : Help("This Assembly demonstrates custom attributes creation and their run-time query.")]
//our custom attribute class
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
//
// TODO: Add constructor logic here
this.description = Description_in;
//
}
protected String description;
public String Description
{
get
{
return this.deescription;
}
}
}
//attaching Help attribute to our AnyClass
[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching Help attribute to our AnyMethod
[Help("This is a do-nothing Method.")]
public void AnyMethod()
{
}
//attaching Help attribute to our AnyInt Field
[Help("This is any Integer.")]
public int AnyInt;
}
class QueryApp
{
public static void Main()
{
}
}
مبحث صفتها بسيار گسترده است و ميتوان ساعتها در مورد صفتهای مختلف بحث نمود. اما آنچه مسلم است تمرين مستمر و پيگيری برنامه نويس در يافتن مواردی که ميتوان با استفاده از صفتها، برنامهای پوياتر ايجاد نمود، مهمترين عامل در فهم و درک کامل مبحث خواهد بود. درباره Reflection نيز در آينده در يان سايت مفصلا توضيح خواهم داد.
خلاصه :
در اين درس با صفتها آشنا شديد. ياد گرفتيد كه چگونه از صفتهاي موجود در .Net Framework استفاده كرده و همچنين چگونه صفتهاي شخصي و دلخواه خود را توليد نماييد. همچنين با پارامترهاي صفتها و انواع آنها و نيز، عناصري كه صفتها بر روي آنها اعمال ميشوند آشنا شديد. در انتهاي درس نيز به مختصر درباره Reflection و چگونگي استفاده از صفتها در زمان اجرا صحبت كرديم. اميدوارم مفيد واقع شده باشد.
زندگي صحنه يکتاي هنرمندي ماست هرکسي نغمه خود خواند و از صحنه رود
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
لطفا سوالات فني را فقط در خود انجمن مطرح بفرماييد، به اين سوالات در PM پاسخ داده نخواهد شد
- پست: 15889
- تاریخ عضویت: جمعه ۷ بهمن ۱۳۸۴, ۷:۵۱ ب.ظ
- سپاسهای ارسالی: 72671 بار
- سپاسهای دریافتی: 31672 بار
- تماس:
درس هفدهم : انواع شمارشي در C#
در اين درس با انواع شمارشي (Enumerator Types) در زبان C# آَشنا خواهيم شد. مطالب مورد بررسي در اين درس به شرح زير ميباشند :
درك و فهم يك نوع شمارشي يا يك enum
ساخت يك نوع شمارشي جديد
چگونگي استفاده از انواع شمارشي
آشنايي با متدهاي مختلف موجود در System.Enum
enmu فرم خاصي از انواع مقداري (Value Type) است كه از System.Enum مشتق شده و امكان پيمايش درون مجموعهاي مشخص را با استفاد از اعداد صحصيح براي ما فراهم مينمايد. با استفاده از enum ميتوان مجموعهاي از مقادير ثابت را تعريف نمود كه اين مقادير ثابت با استفاده از يك عدد صحيح قابل دسترسي هستند.
استفاده از enum در برنامهها باعث بالا رفتن خوانايي برنامه ميشود، چراكه با استفاده از آنها ميتوان با مجموعهاي از اعداد صحيح ترتيبي (Sequential) ، با عناويني تعريف شده، كار كرد. براي مثال، در اعلان يك enum، ما مجموعهاي از نامهاي مورد نظر را تعريف مينماييم و در برنامه ميتوانيم از اين نامها بصورت ترتيبي استفاده نماييم. Enum به هر يك از عناصر موجود در اين مجموعه عددي را تخصيص ميدهد كه شروع اين عدد ميتواند توسط برنامهنويس نيز معين گردد. سپس با استفاده از نام عناصر موجود در enum و يا با استفاده از اعدادي كه به هر يك از اين عناصر تخصيص داده شده، ميتوان enum را پيمايش نمود و به عناصر آن دسترسي داشت.
همانطور كه گفته شد، enum يك نوع مقداري (Value Type) است، از اينرو ارثبري در مورد آن معنايي ندارد. مساوي قرار دادن دو enum نيز، مقادير يكي را در ديگري كپي ميكند. همانطور كه در اين درس، و در منابع ديگر، خواهيد يافت، دسترسي به انواع شمارشي در C# با استفاده از دو كلمه enum و Enum امكان پذير است. در C# نوع شمارشي enum از نوع BCL خود يعني Enum ارثبري ميكند ! با استفاده از enum يك نوع شمارشي جديد توليد ميشود و با استفاده از Enum، ميتوان به پيادهسازي متدهاي استاتيك انواع شمارشي پرداخت.
ايجاد يك نوع شمارشي
.Net Framework BCL حاويenum ها و مثالهاي متعددي از استفادة آنها ميباشد. براي مثال هرگاه كه از MessageBox بر روي فرمي استفاده ميشود، ميتوان از MessageBoxIcon كه يك نوع شمارشي است استفاده نمود.
علاوه بر انواع شمارشي تعريف شده و موجود در .Net Framework، زمانهايي نيز وجود دارند كه ميخواهيم مجموعهاي از عناصر را تعريف كرده و از آنها استفاده نماييم. براي دسترسي به عناصري از نوع صحيح، استفاده از enum باعث خوانا شدن برنامه ميگردد.
نحوه اعلان يك enum در حالت كلي بصورت زير است :
<modifier> enum <enum_name>
{
// Enumeration list
{
در مثال 1-17 كه در زير ملاحظه ميكنيد، نحوه اعلان و استفاده از enum مشخص شده است.
مثال 1-17 : نحوه اعلان يك enum
using System;
// declares the enum
public enum Volume
{
Low,
Medium,
High
}
// demonstrates how to use the enum
class EnumSwitch
{
static void Main()
{
// create and initialize
// instance of enum type
Volume myVolume = Volume.Medium;
// make decision based
// on enum value
switch (myVolume)
{
case Volume.Low:
Console.WriteLine("The volume has been turned Down.");
break;
case Volume.Medium:
Console.WriteLine("The volume is in the middle.");
break;
case Volume.High:
Console.WriteLine("The volume has been turned up.");
break;
}
Console.ReadLine();
}
}
در مثال 1-17 نمونهاي از اعلان يك enum را مشاهده مينماييد. همانطور كه ديده ميشود، اعلان يك نوع شمارشي با استفاده از كلمه كليدي enum صورت گرفته و سپس به دنبال آن نام اين مجموعه مشخص ميشود. درون كروشههاي باز و بسته { } نيز، عناصر نوع شمارشي اعلان ميگردند.
نوع شمارشي توليد شده در اين مثال از نوع Volume است و در متد Main() از آن براي اعلان myVolume استفاده شده است. از آنجائيكه enum يك نوع مقداري است، ميتوانيم بطور مستقيم آنرا مقداردهي نماييم. پس از آنكه متغير myVolume مقداردهي شد، ميتوان همانند ساير انواع مقداري، مانند int، از آن استفاده نمود. در حلقه switch، متغير myVolume با عناصر enum مقايسه ميشوند.
در هربار استفاده از عناصر enum توليد شده، از نام enum توليد شده، در اينجا Volume، در ابتداي نام عنصر استفاده مينماييم (Volume.Medium)، زيرا در صورتيكه در يك برنامه چندين enum وجود داشته باشند كه داراي عناصري با نامهاي يكسان باشند، در صورت عدم استفاده از نام enum مورد نظر قبل از عنصر، ابهام بوجود آمده و دچار مشكل ميشويم.
بطور پيش فرض، به اولين عنصر تعريف شده در enum مقدار صفر تخصيص داده ميشود كه اين مقدار تنها بعنوان انديسي جهت دسترسي به اين عنصر در نظر گرفته ميشود. ساير عناصر enum نيز بطور صعودي مقدار ميگيرند و به هر عنصر يك واحد افزوده ميشود. در مثال 1-17، عنصر Low داراي انديس صفر بوده و ساير عناصر به ترتيب مقدار 1 و 2 خواهند داشت.
در C#، براي موارد خاص ميتوان مقادير پيش فرض در نظر گرفته شده براي عناصر enum را تغيير داد. به مثال زير توجه كنيد.
enum Months
{
jan, feb, mar, apr
}
enum Months
{
jan = 10, feb = 20, mar = 30, apr=40
}
همنطور كه مشاهده ميشود، در اعلان اول، از مقدار پيش فرض استفاده شده، كه در اين حالت jan = 0، feb = 1، mar = 2 و apr = 3 خواهند بود. اما در اعلان دوم، برنامهنويس بنا به نياز خود، تشخيص داده تا به هر يك از عناصر enum مقداري دلخواه را نسبت دهد.
هر چند به تمامي عناصر enum مقداري نسبت داده ميشود ولي از اين مقدار نميتوان بطور مسقيم در تخصيص دهي مقدار به متغيري ديگر استفاده نمود. به مثال زير توجه نماييد :
int x = Months.jan;// اين دستور نادرست است
int x = (int) Months.jan ; //صحيح
براي استفاده از مقدار تخصيص داده شده به عناصر enum، بايد از Casting استفاده نماييم. بدين معنا كه بايد نوع متغييري را كه ميخواهيم مقدار را به ان نسبت دهيم، بايد مشخص شود. در مثال فوق (int) Months.jan معين ميكند كه مقدار تخصيص داده شده به jan به متغييري نسبت داده ميشود كه از نوع int است و يا به عبارت صحيح تر، مقدار تخصيص داده شده به عنصر enum، در فرمت int به متغيير مورد نظر تخصيص داده ميشود.
در ادامه مبحث، توجه شما را به مثالی دیگر درباره enum جلب می نمایم. توجه نمایید که نکات جدیدی نیز در این مثال گنجانده شده اند.
مثال 2-17 : ساخت enum پایه و تخصیص دهی اعضای آن
using System;
// declares the enum
public enum Volume : byte
{
Low = 1,
Medium,
High
}
class EnumBaseAndMembers
{
static void Main()
{
// create and initialize
// instance of enum type
Volume myVolume = Volume.Low;
// make decision based
// on enum value
switch (myVolume)
{
case Volume.Low:
Console.WriteLine("The volume has been turned Down.");
break;
case Volume.Medium:
Console.WriteLine("The volume is in the middle.");
break;
case Volume.High:
Console.WriteLine("The volume has been turned up.");
break;
}
Console.ReadLine();
}
}
با توجه به مثال 2-17 با نحوه تغییر نوع پایه یک enum آشنا می شوید. همانطور که ملاحظه می نمایید، نوع پایه این enum به byte تغییر یافته است. این امر بیان میدارئ که تنها مقادیری از نوع byte قابل تخصیص به عناصر enum هستند.
همانطور که قبلا نیز اشاره شد، مقدار پیش فرض برای اولین عضو enum یعنی Low برابر با صفر است. چون در این مثال مقدار Low را برابر با یک قرار داده ایم، از اینرو مقادیر دو عضو دیگر آن نیز بصورت Middle=2 و High=3 تغییر خواهند یافت.
نکات پیشرفته درباره enum
در زبان C# هر نمونه از enum فضایی معادل با 4 بایت از حافظه را اشغال می نمایند. این مطلب با استفاده از کلمه کلیدی sizeof قابل بررسی لسا. از اینرو از enum میتوان به عنوان یک ساختمان داده مناسب و کارا یاد کرد.
نکته بسیار مهم و جالب در مورد enum در زبان برنامه نویسی C# انست که، برای هر کلاس enum موجود در کد برنامه، مقادیر رشته ای تخصیص داده شده به عناصر enum در یک اسمبلی و بصورت Metadata ذخیره می گردند، از اینرو دسترسی به این مقادیر رشته ای در کد میسر می شود. همچنین می توان از متدهای مختلف مرتبط با enum نیز استفاده نمود. به مثال ساده زیر توجه نمایید :
enum Language
{
CSharp,MCpp,VBNet,JScript,IL
}
class App
{
public static void Main()
{
Console.WriteLine("Write the number of the selected Language");
string[] langAr = Enum.GetNames(Type.GetType("Language"));
for(int i=0;i<langAr.Length;i++)
{
Console.WriteLine(i + "." + langAr[i]);
}
Language myLang=(Language)Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Your Language of choice is: " + myLang);
}
}
در اين درس با انواع شمارشي (Enumerator Types) در زبان C# آَشنا خواهيم شد. مطالب مورد بررسي در اين درس به شرح زير ميباشند :
درك و فهم يك نوع شمارشي يا يك enum
ساخت يك نوع شمارشي جديد
چگونگي استفاده از انواع شمارشي
آشنايي با متدهاي مختلف موجود در System.Enum
enmu فرم خاصي از انواع مقداري (Value Type) است كه از System.Enum مشتق شده و امكان پيمايش درون مجموعهاي مشخص را با استفاد از اعداد صحصيح براي ما فراهم مينمايد. با استفاده از enum ميتوان مجموعهاي از مقادير ثابت را تعريف نمود كه اين مقادير ثابت با استفاده از يك عدد صحيح قابل دسترسي هستند.
استفاده از enum در برنامهها باعث بالا رفتن خوانايي برنامه ميشود، چراكه با استفاده از آنها ميتوان با مجموعهاي از اعداد صحيح ترتيبي (Sequential) ، با عناويني تعريف شده، كار كرد. براي مثال، در اعلان يك enum، ما مجموعهاي از نامهاي مورد نظر را تعريف مينماييم و در برنامه ميتوانيم از اين نامها بصورت ترتيبي استفاده نماييم. Enum به هر يك از عناصر موجود در اين مجموعه عددي را تخصيص ميدهد كه شروع اين عدد ميتواند توسط برنامهنويس نيز معين گردد. سپس با استفاده از نام عناصر موجود در enum و يا با استفاده از اعدادي كه به هر يك از اين عناصر تخصيص داده شده، ميتوان enum را پيمايش نمود و به عناصر آن دسترسي داشت.
همانطور كه گفته شد، enum يك نوع مقداري (Value Type) است، از اينرو ارثبري در مورد آن معنايي ندارد. مساوي قرار دادن دو enum نيز، مقادير يكي را در ديگري كپي ميكند. همانطور كه در اين درس، و در منابع ديگر، خواهيد يافت، دسترسي به انواع شمارشي در C# با استفاده از دو كلمه enum و Enum امكان پذير است. در C# نوع شمارشي enum از نوع BCL خود يعني Enum ارثبري ميكند ! با استفاده از enum يك نوع شمارشي جديد توليد ميشود و با استفاده از Enum، ميتوان به پيادهسازي متدهاي استاتيك انواع شمارشي پرداخت.
ايجاد يك نوع شمارشي
.Net Framework BCL حاويenum ها و مثالهاي متعددي از استفادة آنها ميباشد. براي مثال هرگاه كه از MessageBox بر روي فرمي استفاده ميشود، ميتوان از MessageBoxIcon كه يك نوع شمارشي است استفاده نمود.
علاوه بر انواع شمارشي تعريف شده و موجود در .Net Framework، زمانهايي نيز وجود دارند كه ميخواهيم مجموعهاي از عناصر را تعريف كرده و از آنها استفاده نماييم. براي دسترسي به عناصري از نوع صحيح، استفاده از enum باعث خوانا شدن برنامه ميگردد.
نحوه اعلان يك enum در حالت كلي بصورت زير است :
<modifier> enum <enum_name>
{
// Enumeration list
{
در مثال 1-17 كه در زير ملاحظه ميكنيد، نحوه اعلان و استفاده از enum مشخص شده است.
مثال 1-17 : نحوه اعلان يك enum
using System;
// declares the enum
public enum Volume
{
Low,
Medium,
High
}
// demonstrates how to use the enum
class EnumSwitch
{
static void Main()
{
// create and initialize
// instance of enum type
Volume myVolume = Volume.Medium;
// make decision based
// on enum value
switch (myVolume)
{
case Volume.Low:
Console.WriteLine("The volume has been turned Down.");
break;
case Volume.Medium:
Console.WriteLine("The volume is in the middle.");
break;
case Volume.High:
Console.WriteLine("The volume has been turned up.");
break;
}
Console.ReadLine();
}
}
در مثال 1-17 نمونهاي از اعلان يك enum را مشاهده مينماييد. همانطور كه ديده ميشود، اعلان يك نوع شمارشي با استفاده از كلمه كليدي enum صورت گرفته و سپس به دنبال آن نام اين مجموعه مشخص ميشود. درون كروشههاي باز و بسته { } نيز، عناصر نوع شمارشي اعلان ميگردند.
نوع شمارشي توليد شده در اين مثال از نوع Volume است و در متد Main() از آن براي اعلان myVolume استفاده شده است. از آنجائيكه enum يك نوع مقداري است، ميتوانيم بطور مستقيم آنرا مقداردهي نماييم. پس از آنكه متغير myVolume مقداردهي شد، ميتوان همانند ساير انواع مقداري، مانند int، از آن استفاده نمود. در حلقه switch، متغير myVolume با عناصر enum مقايسه ميشوند.
در هربار استفاده از عناصر enum توليد شده، از نام enum توليد شده، در اينجا Volume، در ابتداي نام عنصر استفاده مينماييم (Volume.Medium)، زيرا در صورتيكه در يك برنامه چندين enum وجود داشته باشند كه داراي عناصري با نامهاي يكسان باشند، در صورت عدم استفاده از نام enum مورد نظر قبل از عنصر، ابهام بوجود آمده و دچار مشكل ميشويم.
بطور پيش فرض، به اولين عنصر تعريف شده در enum مقدار صفر تخصيص داده ميشود كه اين مقدار تنها بعنوان انديسي جهت دسترسي به اين عنصر در نظر گرفته ميشود. ساير عناصر enum نيز بطور صعودي مقدار ميگيرند و به هر عنصر يك واحد افزوده ميشود. در مثال 1-17، عنصر Low داراي انديس صفر بوده و ساير عناصر به ترتيب مقدار 1 و 2 خواهند داشت.
در C#، براي موارد خاص ميتوان مقادير پيش فرض در نظر گرفته شده براي عناصر enum را تغيير داد. به مثال زير توجه كنيد.
enum Months
{
jan, feb, mar, apr
}
enum Months
{
jan = 10, feb = 20, mar = 30, apr=40
}
همنطور كه مشاهده ميشود، در اعلان اول، از مقدار پيش فرض استفاده شده، كه در اين حالت jan = 0، feb = 1، mar = 2 و apr = 3 خواهند بود. اما در اعلان دوم، برنامهنويس بنا به نياز خود، تشخيص داده تا به هر يك از عناصر enum مقداري دلخواه را نسبت دهد.
هر چند به تمامي عناصر enum مقداري نسبت داده ميشود ولي از اين مقدار نميتوان بطور مسقيم در تخصيص دهي مقدار به متغيري ديگر استفاده نمود. به مثال زير توجه نماييد :
int x = Months.jan;// اين دستور نادرست است
int x = (int) Months.jan ; //صحيح
براي استفاده از مقدار تخصيص داده شده به عناصر enum، بايد از Casting استفاده نماييم. بدين معنا كه بايد نوع متغييري را كه ميخواهيم مقدار را به ان نسبت دهيم، بايد مشخص شود. در مثال فوق (int) Months.jan معين ميكند كه مقدار تخصيص داده شده به jan به متغييري نسبت داده ميشود كه از نوع int است و يا به عبارت صحيح تر، مقدار تخصيص داده شده به عنصر enum، در فرمت int به متغيير مورد نظر تخصيص داده ميشود.
در ادامه مبحث، توجه شما را به مثالی دیگر درباره enum جلب می نمایم. توجه نمایید که نکات جدیدی نیز در این مثال گنجانده شده اند.
مثال 2-17 : ساخت enum پایه و تخصیص دهی اعضای آن
using System;
// declares the enum
public enum Volume : byte
{
Low = 1,
Medium,
High
}
class EnumBaseAndMembers
{
static void Main()
{
// create and initialize
// instance of enum type
Volume myVolume = Volume.Low;
// make decision based
// on enum value
switch (myVolume)
{
case Volume.Low:
Console.WriteLine("The volume has been turned Down.");
break;
case Volume.Medium:
Console.WriteLine("The volume is in the middle.");
break;
case Volume.High:
Console.WriteLine("The volume has been turned up.");
break;
}
Console.ReadLine();
}
}
با توجه به مثال 2-17 با نحوه تغییر نوع پایه یک enum آشنا می شوید. همانطور که ملاحظه می نمایید، نوع پایه این enum به byte تغییر یافته است. این امر بیان میدارئ که تنها مقادیری از نوع byte قابل تخصیص به عناصر enum هستند.
همانطور که قبلا نیز اشاره شد، مقدار پیش فرض برای اولین عضو enum یعنی Low برابر با صفر است. چون در این مثال مقدار Low را برابر با یک قرار داده ایم، از اینرو مقادیر دو عضو دیگر آن نیز بصورت Middle=2 و High=3 تغییر خواهند یافت.
نکات پیشرفته درباره enum
در زبان C# هر نمونه از enum فضایی معادل با 4 بایت از حافظه را اشغال می نمایند. این مطلب با استفاده از کلمه کلیدی sizeof قابل بررسی لسا. از اینرو از enum میتوان به عنوان یک ساختمان داده مناسب و کارا یاد کرد.
نکته بسیار مهم و جالب در مورد enum در زبان برنامه نویسی C# انست که، برای هر کلاس enum موجود در کد برنامه، مقادیر رشته ای تخصیص داده شده به عناصر enum در یک اسمبلی و بصورت Metadata ذخیره می گردند، از اینرو دسترسی به این مقادیر رشته ای در کد میسر می شود. همچنین می توان از متدهای مختلف مرتبط با enum نیز استفاده نمود. به مثال ساده زیر توجه نمایید :
enum Language
{
CSharp,MCpp,VBNet,JScript,IL
}
class App
{
public static void Main()
{
Console.WriteLine("Write the number of the selected Language");
string[] langAr = Enum.GetNames(Type.GetType("Language"));
for(int i=0;i<langAr.Length;i++)
{
Console.WriteLine(i + "." + langAr[i]);
}
Language myLang=(Language)Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Your Language of choice is: " + myLang);
}
}
زندگي صحنه يکتاي هنرمندي ماست هرکسي نغمه خود خواند و از صحنه رود
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
لطفا سوالات فني را فقط در خود انجمن مطرح بفرماييد، به اين سوالات در PM پاسخ داده نخواهد شد
- پست: 15889
- تاریخ عضویت: جمعه ۷ بهمن ۱۳۸۴, ۷:۵۱ ب.ظ
- سپاسهای ارسالی: 72671 بار
- سپاسهای دریافتی: 31672 بار
- تماس:
Overload کردن عملگرها در C#
مطالبی که در این قسمت مورد بررسی قرار خواهند گرفت به شرح زیر می باشند :
Overload کردن عملگرها چیست ؟
درک اینکه چه زمانی از Overload کردن عملگرها استفاده می کنیم.
چگونگی Overload کردن عملگرها
قوانین معمول در Overload کردن عملگرها
در این مبحث می خواهیم درباره Overload کردن عملگرهای زبان C# صحبت کنیم. Overload کردن عملگرها بدین معناست که با استفاده از عملگرهای موجود در زبان C#، عمل دیگری بغییر از عمل در نظر گرفته شده برای آن عملگر را برای آن تعریف نماییم. در این مبحث با این مفهوم به طور کامل آشنا خواهید شد.
نگاهی بر Overload کردن عملگرها
همانطور که می دانید، در C# همانند سایر زبانهای برنامه سازی، عملگرهای متعددی وجود دارند (برای بررسی عملگرها می توانید به "عملگرها در C# " مراجعه نمایید.). این عملگرها برای انواع از پیش تعریف شده در زبان C# مورد استفاده قرار می گیرند. اما در موارد مورد نیاز می توان برای انواع تعریف شده توسط کاربر نیز، عملگرهای خاص مورد نظر را تعریف نمود. با استفاده از Overload کردن عملگرها می توان از عملگرهای تعریف شده نیز همانند عملگرهای موجود در زبان استفاده کرد.
برای درک بهتر اهمیت استفاده از Overload کردن عملگرها، فرض کنید می خواهید عملیات ریاضی را بر روی ماتریس ها انجام دهید. برای انجام این کار، مثلا ماتریسی دو بعدی ایجاد می کنید و از آن استفاده می کنید. اما می دانید که می خواهید از این کد تولید شده در برنامه های دیگر نیز استفاده کنید.
برای این منظور، یک نوع جدید با نام Matrix اعلان می کنید که این نوع جدید می تواند یک کلاس و یا یک struct باشد. (برای بررسی کلاسها به " کلاسها در C# " و برای بررسی ساختارها به " ساختارها در C# " رجوع نمایید.) حال که نوع جدیدی با عنوان Matrix را اعلان نموده اید، مسلما می خواهید از روی آن نمونه هایی تولید کرده و عملیات ریاضی نظیر جمع و ضرب را بر روی آنها اجرا نمایید. برای انجام چنین عملیاتی می توان دو متد Add() و Product() را پیاده سازی نمود و از آنها استفاده نمود. مثلا شکل استفاده از این متدها بسته به نحوه آنها می تواند به یکی از شکلهای زیر باشد :
Matrix result = mat1.Add(mat2); // instance
Matrix result = Matrix.Add(mat1, mat2); // static
Matrix result = mat1.DotProduct(mat2).DotProduct(mat3); // and so on...
و یا هر حالت دیگری که می توانید برای آن در نظر بگیرید. تعریف این چنین متدهایی و استفاده از آنها، دشوار، غیر عادی و دشوار است. اما در صورتیکه بتوان برای جمع از + استفاده نمود، حالتی بسیار مناسب رخ می دهد. حال فرض کنید می توانیم از + به جای عمل جمع ماتریسها و از * برای ضرب ماتریسها استفاده کنیم. در اینصورت سه فرمت بالا به شکل زیر در خواهند آمد :
Matrix result = mat1 + mat2;
Matrix result = mat1 * mat2;
Matrix result = mat1 * mat2 * mat3 * mat4;
همانطور که ملاحظه می کنید، استفاده از چنین فرمتی به مراتب آسانتر از تعریف متدهایی برای اجرای عملیاتی این چنین است. همچنین استفاده از این فرمت جدید در مسایل پیچیده و دارای عملیات زیاد، بسیار ساده تر و مطمئن تر است.
موارد نامناسب استفاده از Overload کردن عملگرها
قبل از اینکه بخش پیاده سازی Overload کردن عملگرها بپردازیم لازم است تا بیان داریم که استفاده از Overload کردن عملگرها در همه موارد کارآیی ندارد و می تواند باعث گمراهی شود. بهترین موارد استفاده از Overload کردن عملگرها، مواردی هستند که عملگری که Overload می شود، واقعا بر روی نوع مورد نظر تعریف شده باشد و دارای مفهومی حداقل ریاضی باشد. برای مثال در نظر بگیرید پارکینگی داریم که می خواهیم ورود ماشین در آن را شبیه سازی نماییم. در این حالت Overload کردن عملگر + برای ورود ماشین به پارکینگ مناسب نیست، چراکه در این مورد عملگر + مفهومی نمی تواند داشته باشد. توجه نمایید که بیشتر از Overload کردن عملگرها در مواردی استفاده می شود که به نحوی به مسایل ریاضی مربوط هستند و یا عملگر مورد نظر برای شیء خاص تعریف شده بوده و یا استفاده از این عملگر بر روی شیء، ابهام ایجاد نکند.
عملگرهاي Overload شونده
همانطور که تا کنون ملاحظه نموده اید، تمامی عملگرهای زبان C# دارای پیاده سازی داخلی هستند که می توان از این عملگرها در هر عبارتی استفاده نمود. اما مواردی نیز وجود داشتند که برای سهولت کار می توانیم عملگر خاصی را بطور مورد نظر خود پیاده سازی نماییم. پس از اینکه عملگری Overload شد، پیاده سازی انجام شده توسط کاربر بر پیاده سازی پیش فرض تقدم پیاده کرده و تنها در صورتیکه عملگر Overload شده دارای پیاده سازی نباشد، از پیاده سازی از پیش تعریف شده استفاده خواهد شد. همانطور که می دانید در C# دو نوع عملگر وجود دارد. عملگرهای یگانی (Unary) که قابل Overload کردن هستند به شرح زیر می باشند :
+ - ! ~ ++ -- true false
عملگرهای باینری قابل Overload شدن میز به شرح زیر می باشند :
+ - * / % & | ^ << >> == != > < >= <=
توجه نمایید، هر چند عملگرهای true و false هیچگاه بطور صریح بعنوان عملگر در عبارات استفاده نمیشوند، اما آنها را نیز عملگر میخوانیم چراکه در بسیاری از عبارات منطقی و شرطی از آنها بعنوان عملگر استفاده میگردد.
توجه نمایید که تنها عملگرهای مشخص شده در بالا قابلیت Overload شدن را دارند و نمیتوان سایر عملگرهای زبان C# را Overload نمود. همچنین توجه کنید که Overload کردن یک عملگر باعث Overload شدن ضمني سایر عملگرهای مرتبط با آن نیز میشود. براي مثال Overload كردن عملگر + باعث Overload شدن ضمني عملگر تركيبي += نيز ميشود. البته توجه نماييد كه عملگر انتساب يا = هيچگاه Overload نميشود.
نكته ديگري كه بايد در مورد عملگرهاي Overload شده در نظر گرفت آنست كه، Overload كردن عملگرها خواصي نظير حق تقدم و يا شركتپذيري عملگر را تغيير نميدهد. بعنوان مثال عملگر /، عملگري باينري با حق تقدم مشخص و شركتپذيري از چپ است.
پيادهسازي عملگر Overload شده
پيادهسازي يك عملگر Overload شده تقريباً شبيه به پيادهسازي متدي استاتيك است، با اين تفاوت كه در اينجا لازم است از كلمه كليدي operator و عملگر مورد نظر استفاده نماييم. در زير نمونهاي از ساختار كلي عملگر ضرب كه براي ماتريسها در نظر گرفته بوديم را مشاهده مينماييد.
public static Matrix operator *(Matrix mat1, Matrix mat2)
{
// dot product implementation
}
همانطور كه مشاهده ميكنيد، متد استفاده شده حتماً بايد بطور استاتيك تعريف گردد. از كلمه كليدي operator نيز پس نوعي كه ميخواهيم براي آن عملگري را Overload نماييم، قرار ميگيرد كه در اينجا Matrix نوع مورد نظر ما است. پس از كلمه كليدي operator، عملگري كه ميخواهيم Overload كنيم را قرار داده و سپس پارامترهايي كه عملگر بر روي آنها اعمال ميشوند را قرار ميدهيم. در مثال 1-18، نمونهاي از Overload كردن عملگرها را مشاهده خواهيد كرد.
مثال 1-18 : نمونهاي از Overload كردن عملگرها
using System;
class Matrix3D
{
public const int DIMSIZE = 3;
private double[,] matrix = new double[DIMSIZE, DIMSIZE];
// امكان تخصيص مقدار را براي فراخواننده فراهم ميكند.
public double this[int x, int y]
{
get { return matrix[x, y]; }
set { matrix[x, y] = value; }
}
// كردن عملگر + براي استفاده بر روي ماتريسهاOverload
public static Matrix3D operator +(Matrix3D mat1, Matrix3D mat2)
{
Matrix3D newMatrix = new Matrix3D();
for (int x=0; x < DIMSIZE; x++)
for (int y=0; y < DIMSIZE; y++)
newMatrix[x, y] = mat1[x, y] + mat2[x, y];
return newMatrix;
}
}
class MatrixTest
{
// از آن استفاده ميشود.InitMatrixدر متد
public static Random rand = new Random();
static void Main()
{
Matrix3D mat1 = new Matrix3D();
Matrix3D mat2 = new Matrix3D();
// ماتريسها با مقادير تصادفي مقداردهي ميشوند.
InitMatrix(mat1);
InitMatrix(mat2);
// ماتريسها در خروجي نمايش داده ميشوند.
Console.WriteLine("Matrix 1: ");
PrintMatrix(mat1);
Console.WriteLine("Matrix 2: ");
PrintMatrix(mat2);
// عمل جمع ماتريسها صورت گرفته و نتيجه محاسبه ميگردد.
Matrix3D mat3 = mat1 + mat2;
Console.WriteLine();
Console.WriteLine("Matrix 1 + Matrix 2 = ");
PrintMatrix(mat3);
}
// متدي كه در آن ماتريسها با مقادير تصادفي مقداردهي ميشوند.
public static void InitMatrix(Matrix3D mat)
{
for (int x=0; x < Matrix3D.DIMSIZE; x++)
for (int y=0; y < Matrix3D.DIMSIZE; y++)
mat[x, y] = rand.NextDouble();
}
// متد چاپ ماتريس در خروجي.
public static void PrintMatrix(Matrix3D mat)
{
Console.WriteLine();
for (int x=0; x < Matrix3D.DIMSIZE; x++)
{
Console.Write("[ ");
for (int y=0; y < Matrix3D.DIMSIZE; y++)
{
// فرمتدهي خروجي.
Console.Write("{0,8:#.000000}", mat[x, y]);
if ((y+1 % 2) < 3)
Console.Write(", ");
}
Console.WriteLine(" ]");
}
Console.WriteLine();
}
}
در مثال 1-18، عملگر + مورد Overload شدن قرار گرفته است. براي تمركز بيشتر بر روي كد، قسمت مربوط به Overload شدن عملگر + را در زير آوردهام :
public static Matrix3D operator +(Matrix3D mat1, Matrix3D mat2)
{
Matrix3D newMatrix = new Matrix3D();
for (int x=0; x < DIMSIZE; x++)
for (int y=0; y < DIMSIZE; y++)
newMatrix[x, y] = mat1[x, y] + mat2[x, y];
return newMatrix;
}
عملگر هميشه بطور استاتيك اعلان ميشود، چراكه متعلق به يك نوع كلي است و مربوط به نمونهاي خاص نميباشد. نوع بازگشتي، Matrix3D است و تنها چيزي كه اين متد را از يك متد عادي متمايز مينمايد استفاده از كلمه كليدي operator و عملگر + است. پيادهسازي عملگر Overload شده باعث ايجاد نمونهاي جديد از Matrix3D شده و عمل جمع ماتريس را انجام ميدهد.
نكاتي چند در مورد Overload كردن عملگرها
زبان C# قوانيني براي Overload كردن عملگرها اعمال ميكند. يكي از اين قوانين آنست كه عملگر Overload شده بايد از نوعي كه مورد استفاده قرار ميگيرد، اعلان شود.
قانون بعدي اينست كه به هنگام پيادهسازي عملگرهاي مقايسهاي نظير >، < و ==، بايد حالتهاي تركيبي آنها را نيز پيادهسازي نمود. براي مثال در صورتيكه عملگر > را Overload ميكنيد، بايد عملگر >= را پيادهسازي نماييد.
نكته ديگر اينكه، پس از Overload كردن عملگرها، عملگرهاي تركيبي آنها نيز قابل استفاده هستند. توجه نماييد كه عملگرهاي مقايسهاي كه در بالا اشاره شد از اين قاعده مستثنا هستند. همانطور كه در قبل نيز اشاره شد، پيادهسازي عملگر + باعث ميشود تا بتوان از += نيز استفاده نمود.
مطالبی که در این قسمت مورد بررسی قرار خواهند گرفت به شرح زیر می باشند :
Overload کردن عملگرها چیست ؟
درک اینکه چه زمانی از Overload کردن عملگرها استفاده می کنیم.
چگونگی Overload کردن عملگرها
قوانین معمول در Overload کردن عملگرها
در این مبحث می خواهیم درباره Overload کردن عملگرهای زبان C# صحبت کنیم. Overload کردن عملگرها بدین معناست که با استفاده از عملگرهای موجود در زبان C#، عمل دیگری بغییر از عمل در نظر گرفته شده برای آن عملگر را برای آن تعریف نماییم. در این مبحث با این مفهوم به طور کامل آشنا خواهید شد.
نگاهی بر Overload کردن عملگرها
همانطور که می دانید، در C# همانند سایر زبانهای برنامه سازی، عملگرهای متعددی وجود دارند (برای بررسی عملگرها می توانید به "عملگرها در C# " مراجعه نمایید.). این عملگرها برای انواع از پیش تعریف شده در زبان C# مورد استفاده قرار می گیرند. اما در موارد مورد نیاز می توان برای انواع تعریف شده توسط کاربر نیز، عملگرهای خاص مورد نظر را تعریف نمود. با استفاده از Overload کردن عملگرها می توان از عملگرهای تعریف شده نیز همانند عملگرهای موجود در زبان استفاده کرد.
برای درک بهتر اهمیت استفاده از Overload کردن عملگرها، فرض کنید می خواهید عملیات ریاضی را بر روی ماتریس ها انجام دهید. برای انجام این کار، مثلا ماتریسی دو بعدی ایجاد می کنید و از آن استفاده می کنید. اما می دانید که می خواهید از این کد تولید شده در برنامه های دیگر نیز استفاده کنید.
برای این منظور، یک نوع جدید با نام Matrix اعلان می کنید که این نوع جدید می تواند یک کلاس و یا یک struct باشد. (برای بررسی کلاسها به " کلاسها در C# " و برای بررسی ساختارها به " ساختارها در C# " رجوع نمایید.) حال که نوع جدیدی با عنوان Matrix را اعلان نموده اید، مسلما می خواهید از روی آن نمونه هایی تولید کرده و عملیات ریاضی نظیر جمع و ضرب را بر روی آنها اجرا نمایید. برای انجام چنین عملیاتی می توان دو متد Add() و Product() را پیاده سازی نمود و از آنها استفاده نمود. مثلا شکل استفاده از این متدها بسته به نحوه آنها می تواند به یکی از شکلهای زیر باشد :
Matrix result = mat1.Add(mat2); // instance
Matrix result = Matrix.Add(mat1, mat2); // static
Matrix result = mat1.DotProduct(mat2).DotProduct(mat3); // and so on...
و یا هر حالت دیگری که می توانید برای آن در نظر بگیرید. تعریف این چنین متدهایی و استفاده از آنها، دشوار، غیر عادی و دشوار است. اما در صورتیکه بتوان برای جمع از + استفاده نمود، حالتی بسیار مناسب رخ می دهد. حال فرض کنید می توانیم از + به جای عمل جمع ماتریسها و از * برای ضرب ماتریسها استفاده کنیم. در اینصورت سه فرمت بالا به شکل زیر در خواهند آمد :
Matrix result = mat1 + mat2;
Matrix result = mat1 * mat2;
Matrix result = mat1 * mat2 * mat3 * mat4;
همانطور که ملاحظه می کنید، استفاده از چنین فرمتی به مراتب آسانتر از تعریف متدهایی برای اجرای عملیاتی این چنین است. همچنین استفاده از این فرمت جدید در مسایل پیچیده و دارای عملیات زیاد، بسیار ساده تر و مطمئن تر است.
موارد نامناسب استفاده از Overload کردن عملگرها
قبل از اینکه بخش پیاده سازی Overload کردن عملگرها بپردازیم لازم است تا بیان داریم که استفاده از Overload کردن عملگرها در همه موارد کارآیی ندارد و می تواند باعث گمراهی شود. بهترین موارد استفاده از Overload کردن عملگرها، مواردی هستند که عملگری که Overload می شود، واقعا بر روی نوع مورد نظر تعریف شده باشد و دارای مفهومی حداقل ریاضی باشد. برای مثال در نظر بگیرید پارکینگی داریم که می خواهیم ورود ماشین در آن را شبیه سازی نماییم. در این حالت Overload کردن عملگر + برای ورود ماشین به پارکینگ مناسب نیست، چراکه در این مورد عملگر + مفهومی نمی تواند داشته باشد. توجه نمایید که بیشتر از Overload کردن عملگرها در مواردی استفاده می شود که به نحوی به مسایل ریاضی مربوط هستند و یا عملگر مورد نظر برای شیء خاص تعریف شده بوده و یا استفاده از این عملگر بر روی شیء، ابهام ایجاد نکند.
عملگرهاي Overload شونده
همانطور که تا کنون ملاحظه نموده اید، تمامی عملگرهای زبان C# دارای پیاده سازی داخلی هستند که می توان از این عملگرها در هر عبارتی استفاده نمود. اما مواردی نیز وجود داشتند که برای سهولت کار می توانیم عملگر خاصی را بطور مورد نظر خود پیاده سازی نماییم. پس از اینکه عملگری Overload شد، پیاده سازی انجام شده توسط کاربر بر پیاده سازی پیش فرض تقدم پیاده کرده و تنها در صورتیکه عملگر Overload شده دارای پیاده سازی نباشد، از پیاده سازی از پیش تعریف شده استفاده خواهد شد. همانطور که می دانید در C# دو نوع عملگر وجود دارد. عملگرهای یگانی (Unary) که قابل Overload کردن هستند به شرح زیر می باشند :
+ - ! ~ ++ -- true false
عملگرهای باینری قابل Overload شدن میز به شرح زیر می باشند :
+ - * / % & | ^ << >> == != > < >= <=
توجه نمایید، هر چند عملگرهای true و false هیچگاه بطور صریح بعنوان عملگر در عبارات استفاده نمیشوند، اما آنها را نیز عملگر میخوانیم چراکه در بسیاری از عبارات منطقی و شرطی از آنها بعنوان عملگر استفاده میگردد.
توجه نمایید که تنها عملگرهای مشخص شده در بالا قابلیت Overload شدن را دارند و نمیتوان سایر عملگرهای زبان C# را Overload نمود. همچنین توجه کنید که Overload کردن یک عملگر باعث Overload شدن ضمني سایر عملگرهای مرتبط با آن نیز میشود. براي مثال Overload كردن عملگر + باعث Overload شدن ضمني عملگر تركيبي += نيز ميشود. البته توجه نماييد كه عملگر انتساب يا = هيچگاه Overload نميشود.
نكته ديگري كه بايد در مورد عملگرهاي Overload شده در نظر گرفت آنست كه، Overload كردن عملگرها خواصي نظير حق تقدم و يا شركتپذيري عملگر را تغيير نميدهد. بعنوان مثال عملگر /، عملگري باينري با حق تقدم مشخص و شركتپذيري از چپ است.
پيادهسازي عملگر Overload شده
پيادهسازي يك عملگر Overload شده تقريباً شبيه به پيادهسازي متدي استاتيك است، با اين تفاوت كه در اينجا لازم است از كلمه كليدي operator و عملگر مورد نظر استفاده نماييم. در زير نمونهاي از ساختار كلي عملگر ضرب كه براي ماتريسها در نظر گرفته بوديم را مشاهده مينماييد.
public static Matrix operator *(Matrix mat1, Matrix mat2)
{
// dot product implementation
}
همانطور كه مشاهده ميكنيد، متد استفاده شده حتماً بايد بطور استاتيك تعريف گردد. از كلمه كليدي operator نيز پس نوعي كه ميخواهيم براي آن عملگري را Overload نماييم، قرار ميگيرد كه در اينجا Matrix نوع مورد نظر ما است. پس از كلمه كليدي operator، عملگري كه ميخواهيم Overload كنيم را قرار داده و سپس پارامترهايي كه عملگر بر روي آنها اعمال ميشوند را قرار ميدهيم. در مثال 1-18، نمونهاي از Overload كردن عملگرها را مشاهده خواهيد كرد.
مثال 1-18 : نمونهاي از Overload كردن عملگرها
using System;
class Matrix3D
{
public const int DIMSIZE = 3;
private double[,] matrix = new double[DIMSIZE, DIMSIZE];
// امكان تخصيص مقدار را براي فراخواننده فراهم ميكند.
public double this[int x, int y]
{
get { return matrix[x, y]; }
set { matrix[x, y] = value; }
}
// كردن عملگر + براي استفاده بر روي ماتريسهاOverload
public static Matrix3D operator +(Matrix3D mat1, Matrix3D mat2)
{
Matrix3D newMatrix = new Matrix3D();
for (int x=0; x < DIMSIZE; x++)
for (int y=0; y < DIMSIZE; y++)
newMatrix[x, y] = mat1[x, y] + mat2[x, y];
return newMatrix;
}
}
class MatrixTest
{
// از آن استفاده ميشود.InitMatrixدر متد
public static Random rand = new Random();
static void Main()
{
Matrix3D mat1 = new Matrix3D();
Matrix3D mat2 = new Matrix3D();
// ماتريسها با مقادير تصادفي مقداردهي ميشوند.
InitMatrix(mat1);
InitMatrix(mat2);
// ماتريسها در خروجي نمايش داده ميشوند.
Console.WriteLine("Matrix 1: ");
PrintMatrix(mat1);
Console.WriteLine("Matrix 2: ");
PrintMatrix(mat2);
// عمل جمع ماتريسها صورت گرفته و نتيجه محاسبه ميگردد.
Matrix3D mat3 = mat1 + mat2;
Console.WriteLine();
Console.WriteLine("Matrix 1 + Matrix 2 = ");
PrintMatrix(mat3);
}
// متدي كه در آن ماتريسها با مقادير تصادفي مقداردهي ميشوند.
public static void InitMatrix(Matrix3D mat)
{
for (int x=0; x < Matrix3D.DIMSIZE; x++)
for (int y=0; y < Matrix3D.DIMSIZE; y++)
mat[x, y] = rand.NextDouble();
}
// متد چاپ ماتريس در خروجي.
public static void PrintMatrix(Matrix3D mat)
{
Console.WriteLine();
for (int x=0; x < Matrix3D.DIMSIZE; x++)
{
Console.Write("[ ");
for (int y=0; y < Matrix3D.DIMSIZE; y++)
{
// فرمتدهي خروجي.
Console.Write("{0,8:#.000000}", mat[x, y]);
if ((y+1 % 2) < 3)
Console.Write(", ");
}
Console.WriteLine(" ]");
}
Console.WriteLine();
}
}
در مثال 1-18، عملگر + مورد Overload شدن قرار گرفته است. براي تمركز بيشتر بر روي كد، قسمت مربوط به Overload شدن عملگر + را در زير آوردهام :
public static Matrix3D operator +(Matrix3D mat1, Matrix3D mat2)
{
Matrix3D newMatrix = new Matrix3D();
for (int x=0; x < DIMSIZE; x++)
for (int y=0; y < DIMSIZE; y++)
newMatrix[x, y] = mat1[x, y] + mat2[x, y];
return newMatrix;
}
عملگر هميشه بطور استاتيك اعلان ميشود، چراكه متعلق به يك نوع كلي است و مربوط به نمونهاي خاص نميباشد. نوع بازگشتي، Matrix3D است و تنها چيزي كه اين متد را از يك متد عادي متمايز مينمايد استفاده از كلمه كليدي operator و عملگر + است. پيادهسازي عملگر Overload شده باعث ايجاد نمونهاي جديد از Matrix3D شده و عمل جمع ماتريس را انجام ميدهد.
نكاتي چند در مورد Overload كردن عملگرها
زبان C# قوانيني براي Overload كردن عملگرها اعمال ميكند. يكي از اين قوانين آنست كه عملگر Overload شده بايد از نوعي كه مورد استفاده قرار ميگيرد، اعلان شود.
قانون بعدي اينست كه به هنگام پيادهسازي عملگرهاي مقايسهاي نظير >، < و ==، بايد حالتهاي تركيبي آنها را نيز پيادهسازي نمود. براي مثال در صورتيكه عملگر > را Overload ميكنيد، بايد عملگر >= را پيادهسازي نماييد.
نكته ديگر اينكه، پس از Overload كردن عملگرها، عملگرهاي تركيبي آنها نيز قابل استفاده هستند. توجه نماييد كه عملگرهاي مقايسهاي كه در بالا اشاره شد از اين قاعده مستثنا هستند. همانطور كه در قبل نيز اشاره شد، پيادهسازي عملگر + باعث ميشود تا بتوان از += نيز استفاده نمود.
زندگي صحنه يکتاي هنرمندي ماست هرکسي نغمه خود خواند و از صحنه رود
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
صحنه پيوسته به جاست خرم آن نغمه که مردم بسپارند به ياد
[External Link Removed for Guests] | [External Link Removed for Guests] | مجله الکترونيکي سنترال کلابز
[External Link Removed for Guests] | [External Link Removed for Guests] | [External Link Removed for Guests]
لطفا سوالات فني را فقط در خود انجمن مطرح بفرماييد، به اين سوالات در PM پاسخ داده نخواهد شد
- پست: 60
- تاریخ عضویت: جمعه ۱۴ بهمن ۱۳۸۴, ۱:۱۱ ب.ظ
- سپاسهای ارسالی: 2 بار
- سپاسهای دریافتی: 9 بار
- پست: 60
- تاریخ عضویت: جمعه ۱۴ بهمن ۱۳۸۴, ۱:۱۱ ب.ظ
- سپاسهای ارسالی: 2 بار
- سپاسهای دریافتی: 9 بار
- پست: 883
- تاریخ عضویت: سهشنبه ۱۴ اسفند ۱۳۸۶, ۱:۳۰ ب.ظ
- سپاسهای ارسالی: 112 بار
- سپاسهای دریافتی: 327 بار
- تماس:
marilyn_manson نوشته شده:لطفا کتابي جهت آموزش برنامه نويسي ÷ايگاه داده در #c معرفي کنيد
لاتين يا فارسي بصورت pdf ! فوري
عنوان: برنامه نويسي بانک هاي اطلاعاتي در 0.2 C [سي شارپ ۰/۲]
مترجم: مترجم رامين مولاناپور
محلنشر: تهران
ناشر: موسسه فرهنگي هنري ديباگران تهران
تاريخنشر: ۱۳۸۴
عنوان اصلي: Mastering C database programming
موضوعها: ۱. سي شارپ( زبان برنامه نويسي کامپيوتر)
۲. پايگاههاي اطلاعاتي -- مديريت
[External Link Removed for Guests]
آموزش شارژ کارتریج و تعمیر انواع (راهنمای تعمیر لپ تاپ و پرینتر ، ...)
[External Link Removed for Guests]
آموزش شارژ کارتریج و تعمیر انواع (راهنمای تعمیر لپ تاپ و پرینتر ، ...)
[External Link Removed for Guests]