برگزیده های پرشین تولز

کار با سی شارپ

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
به دليل اينكه كلاسها و ساختارهايي كه از 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() انجام گرفته است. توجه نماييد كه پياده‌سازي متد بايد دقيقا از لحاظ نوع بازگشتي و تعداد و نوع پارامترها شبيه به اعلان موجود در واسط باشد، كوچكترين تغييري باعث ايجاد خطاي كامپايلر مي‌شود. مثال زير نحوه ارث‌بري واسطها از يكديگر نيز نمايش داده شده است.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
مثال 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 نيز ارث‌بري دارد، از اينرو بايد كليه اعضاي اين دو واسط را پياده‌سازي نمايد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
چند نكته مهم :



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 آورده شده است.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
مثال 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- كلاسي كه از واسط ارث‌بري مي‌كند بايد تمامي متدهاي آنرا دقيقا همان گونه كه در واسط مشخص شده پياده‌سازي نمايد. به بيان كلي، كلاسي كه از يك واسط ارث مي‌برد، فرم و ساختار كلي خود را از واسط مي‌گيرد و نحوه رفتار و پياده‌سازي آنرا خود انجام مي‌دهد.





خلاصه :

در اين درس با مفاهيم كلي و اصلي درباره واسطها آشنا شديد. هم اكنون مي‌دانيد كه واسطها چه هستند و سودمندي استفاده از آنها چيست. همچنين نحوه پياده‌سازي واسط و ارث‌بري از آنرا آموختيد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
نكته مهم قبل از مطالعه اين درس
توجه نماييد، delegate ها و رخدادها بسيار با يكديگر در تعامل‌اند، از اينرو در برخي موارد، قبل از آموزش و بررسي رخدادها، به ناچار، از آنها نيز استفاده شده و يا به آنها رجوع شده است. رخدادها در قسمت انتهايي اين درس مورد بررسي قرار مي‌گيرند، از اينرو در صورتيكه در برخي موارد دچار مشكل شديد و يا درك مطلب برايتان دشوار بود، ابتدا كل درس را تا انتها مطالعه نماييد و سپس در بار دوم با ديدي جديد به مطالب و مفاهيم موجود در آن نگاه كنيد. در اغلب كتابهاي آموزشي زبان C# نيز ايندو مفهوم با يكديگر آورده شده‌اند ولي درك رخدادها مستلزم درك و فراگيري كامل delegate هاست، از اينرو مطالب مربوط به 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 ها، همچنين مي‌توان متدهايي تعريف نمود كه تنها در زمان اجرا قابل دسترسي باشند.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
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 را درك كنيد.)
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
مثال 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() ارسال نماييد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
درك سودمندي 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 خواهيم پرداخت.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
حل مسئله با استفاده از 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();

}
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
ساخت نمونه‌هاي جديد از يك 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 را بطور كامل درك نماييد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
رخدادها (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 به رخداد مرتبط شده است. (يا در اصطلاح در رخداد ثبت شده است.)
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
مثال فوق را بدون استفاده از رخداد نيز مي‌توان نوشت. اين نسخه از مثال 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 ها بسيار قدرتمند ظاهر مي‌شوند. بدست آوردن مهارت در ايجاد و استفاده از آنها نياز به تمرين و تفكر بسيار دارد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
ستثناها، در حقيقت خطاهاي غير منتظره در برنامه‌هاي ما هستند. اکثراً، مي‌توان و بايد روشهايي را جهت برخورد با خطاهای موجود در برنامه در نظر گرفت و آنها را پياده‌سازی کرد. بعنوان مثال، بررسي و تاييد داده‌های ورودی کاربران، بررسی اشياء تهی يا 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 برای کاربر بر روی کنسول نمايش داده مي‌شود.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
نکته : توجه نماييد که کليه مثالهای موجود در اين درس به طور تعمدی دارای خطاهايی هستند تا شما با نحوه مقابله با استثناها آشنا شويد.



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 مورد استفاده خاص تر و جزئی تر باشند، پيغامها و اطلاعات مفيدتری در مورد خطا مي‌توان بدست آورد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
استثناهايی که مورد بررسی قرار نگيرند، در بالای 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 مطمئن باشيد که که منابع تخصيص داده شده به برنامه، به سيستم باز خواهند گشت چراکه اين بلوک حتما اجرا مي‌شود و مي‌توان کدهای مهمی را که مي‌خواهيم تحت هر شرايطی اجرا شوند را درون آن قرار داد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
صفتها در حقيقت اطلاعات توضيحی هستند که مي‌توانيد آنها را به برنامه‌های خود بيفزاييد. صفتها را مي‌توان برای کليه عناصر برنامه از قبيل کلاسها، واسطها، اسمبلی ها و ... مورد استفاده قرار داد. از اين اطلاعات مي‌توان برای موارد متنوعی در زمان اجرای برنامه استفاده نمود. برای مثال مي‌توان به صفتی مانند DllImportAttribute اشاره کرد که امکان برقراری ارتباط با توابع کتابخانه‌ای Win32 را فراهم مي‌نمايد. همچنين صفتهايي نيز وجود دارند که برنامه‌نويس يا توسعه دهنده برنامه را در امر توليد برنامه ياری مي‌نمايند. برای مثال مي‌توان به صفت ObsoleteAttribute اشاره کرد که با استفاده از آن، در زمان کامپايل برنامه پيغامی برای برنامه نويس نمايش داده مي‌شود و مشخص مي‌کند که متدی خاص مورد استفاده قرار نگرفته و يا ديگر مورد استفاده نيست. همچنين هنگاميکه با فرمهای ويندوز کار مي‌کنيم، صفتهای بسياری وجود دارند که امکان استفاده از اين فرمها را فراهم کرده و باعث مي‌شوند تا اطلاعات مربوط به اين عناصر در property فرم ظاهر شوند. يکی ديگر از موارد استفاده از صفتها در مسايل امنيتی اسمبلي‌های .Net است. برای مثال صفتهايي وجود دارند که باعث جلوگيری از فراخواني‌های غير مجاز مي‌شوند، بدين معنی که تنها اجازه فراخوانی را به متدها يا اشيايي مي‌دهند که قبلا تعريف شده و مشخص شده باشند.



يکی از علتهای استفاده از صفتها آنست که، اغلب سرويسهايي را که آنها برای کاربر فراهم مي‌نمايند، بسيار پيچيده است و با کدهای معمولی نمي‌توان آنرا را بدست آورد. از اينرو استفاده از صفتها در بسياری از موارد ضروری و اجتناب ناپذير است. همانطور که خواهيد ديد، صفتها به برنامه‌هاي ما Metadata اضافه مي‌نمايند. پس از کامپايل برنامه‌های C#، فايل اسمبلی برای آن ايجاد مي‌گردد که اين اسمبلی معمولا يا يک فايل اجرايي است و يا يک Dll است. توصيف اسمبلی، در Metadata ي مربوط به آن قرار مي‌گيرد. طی پروسه‌ای تحت عنوان Reflection، صفت يک برنامه از طريق فايل Metadata ي موجود در اسمبلی آن قابل دسترس مي‌گردد. .(برای آشنايي بيشتر با اسمبلی و Metadata مي‌توانيد به " کامپايل يک برنامه سی شارپ " در همين سايت مراجعه نماييد.) در حقيقت صفتها، کلاسهايي هستند که مي‌توانيد آنها را با زبان C# توليد کرده و جهت افزودن اطلاعاتی توضيحی به کد خود، از آنها استفاده نماييد. اين اطلاعات در زمان اجرای برنامه از طريق Reflection قابل دسترسی هستند.



در اين درس با روش استفاده از صفتها و چگونگی ارتباط دادن آنها با عناصر مختلف برنامه آشنا خواهيد شد.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
مفاهيم اوليه درباره صفتها



صفتها را معمولا قبل از اعلان عنصر مورد نظر در برنامه قرار مي‌دهند. اعلان صفتها بدين صورت است که نام صفت درون دو براکت قرار مي‌گيرد.

[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();
    }
}
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
همانطور که در مثال 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.'


همانطور که ملاحظه مي‌کنيد، سومين اعلان صفت در اين برنامه که با پارامتر همراه بود، باعث شده است تا پارامتر صفت نيز به عنوان بخشی از پيام نمايش داده شده توسط کامپايلر، نشان داده شود. در مورد دو صفت ديگر نيز مشاهده مي‌شود که تنها پيغامی ساده توليد گرديده است.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
مثال 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'


نکته مهمی که بايد در مورد صفتها در نظر بگيريد آنست که اطلاعاتی که توسط صفت در کد برنامه قرار مي‌گيرد، توسط ساير برنامه‌ها نيز قابل تفسير و استفاده است.
 

>-->O

همکار بازنشسته
تاریخ عضویت
25 نوامبر 2009
نوشته‌ها
2,530
لایک‌ها
468
محل سکونت
㋡ همین جا ㋡
انواع پارامترهای صفت (پارامترهای 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 کاملا اختياری هستند و هميشه نيازی به استفاده از آنها نمي‌باشد.
 
بالا