آموزش سی شارپ c# درس چهاردهم

 
ارسال شده در تاریخ 1392/7/22 توسط admin در زمینه آموزش سی شارپ
 
درس چهاردهم - رخدادها و 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# آشنا خواهید شد .
 
www.cPoroje.ir
 
مقدمه ای بر رخدا دها و 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 ی را به الگوریتم مورد نظر ارسال نمود و اجازه د اد تا متد موجود در آن، عمل مورد نظر ما را انجام دهد . چنین عملی در مثال 14-1 نشان داده شده است .
)به صورت مسئله توجه نمایید : میخواهیم مجموعه ای از اشیاء را که در یک ساختمان داده قرار گرفت ه اند را مرتب نمائ یم . برای اینکار نیاز به مقایسه این اشیاء با یکدیگر د اریم . از آنجائیکه این اشیاء از انواع (type) مختلف هستند به الگوریتمی نیاز داریم تا بتواند مقایسه بین اشیاء نظیر را انجام دهد . با استفاده از روشهای معمول این کار امکان پذیر نیست، چراکه نمی توان اشیائﺊ از انواع مختلف را با یکدیگر مقایسه کرد . برای مثال شما نم یتوانید نوع عددی int را با نوع رشت های string مقایسه نمایید . به همین
دلیل با استفاده از delegate ها به حل مسئله پرداخت ه ایم . به مثال زیر به دقت توجه نمایید تا بتوانید به درستی مفهوم delegate را دری کنید (.
مثال : 14-1 اعلان و پی ادهسازی یک 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(" Before Sort: "); sd.PrintNames();
 
sd.Sort(cmp);
Console.WriteLine(" After Sort: "); 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: "); foreach (Name name in names)
{
Console.WriteLine(name.ToString());
}
}
}
 
اولین اعلان در این برنامه، اعلان delegate است . اعلان delegate بسیا رشبیه به اعلان متد است، با این تفاوت که دارای کلمه کلیدی delegate در اعلان است و در انتهای اعلان آن ";" قرار می گیرد و نیز پیاده سازی ندارد . در زیر اعلان delegate که در مثال 14-1 آورده شده را مشاهده می نمایید : www.cPoroje.ir
 
public delegate int Comparer(object obj1, object obj2);
 
این اعلان، مدل متدی را که delegate می تواند به آن اشاره کند را تع ریف مینماید . متدی که می توان از آن بعنوان delegate handler برای Comparer استفاده نمود، هر متدی می تواند باشد اما حتمًا باید پارامتر اول و دوم آن از نوع object بوده و مقداری از نوع int بازگرداند . در زیر متدی که بعنوان delegate handler در مثال 14-1 مورد استفاده قرار گرفته است، نشان داده شده است :
public static int ComparerFirstNames(object name1, object name2)
{
...
}
 
برای استفاده از delegate می بایست نمونه ای از آن ایجاد کن ید . ایجاد نمونه جدید از delegate همانند ایجاد نمونهای جدید از یک کلاس است که به همراه پارامتری جهت تعیین متد delegate handler ایجاد میشود :
Comparer cmp = new Comparer(Name.ComparerFirstName);
 
در مثال 14-1 ، 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 را به این واسط مرتبط نمود . www.cPoroje.ir
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 را بطور کامل دری نمایید .

www.cPoroje.ir

Copyright © 2014 icbc.ir