مقدمه
در درس قبل دیدیم که چگونه میتوان با استفاده از یک نمایندهی (delegate) عمومی، الگوی ناشر-مشترک
را برای ارسال اعلان پیادهسازی کرد. این الگو بسیار قدرتمند است، اما یک ضعف بزرگ دارد: وقتی یک
نماینده به صورت public تعریف میشود، هر کدی از خارج کلاس میتواند آن را به طور کامل
بازنویسی
کند (با عملگر `=`)، یا حتی آن را مستقیماً فراخوانی (Invoke) کند. این کار اصل کپسولهسازی
را
نقض میکند، زیرا کنترل اعلانها از دست کلاس ناشر خارج میشود. برای حل این مشکل، C# یک
مکانیزم سطح بالاتر و امنتر به نام رویداد (Event) را فراهم کرده است. یک رویداد
در واقع یک "پوشش محافظ" در اطراف یک نماینده است که دسترسی به آن را محدود میکند.
رویداد (Event) چیست؟
یک رویداد مکانیزمی است که به یک کلاس (ناشر) اجازه میدهد تا به دیگر کلاسها (مشترکین) در مورد
وقوع یک اتفاق مهم اطلاعرسانی کند. مشترکین میتوانند متدهایی را برای "گوش دادن" به این رویداد
ثبتنام کنند. وقتی ناشر رویداد را "برمیانگیزد" یا "آتش میکند" (Raise/Fire)، تمام متدهای
ثبتنام شده به طور خودکار فراخوانی میشوند.
در پشت صحنه، هر رویداد توسط یک نماینده پشتیبانی میشود. کلمهی کلیدی event به سادگی دسترسی به آن نماینده را محدود میکند:
- از خارج از کلاس، شما فقط میتوانید با عملگرهای += (برای اشتراک) و -=
(برای لغو اشتراک) با رویداد کار کنید.
- فقط خود کلاسی که رویداد را تعریف کرده، میتواند آن را برانگیزد (فراخوانی
کند).
پیادهسازی الگو با رویدادها
بیایید مثال FileDownloader از درس قبل را با استفاده از event بازنویسی کنیم تا
امنیت و
کپسولهسازی آن را بهبود بخشیم.
FileDownloader.cs
public class FileDownloader
{
public event Action<int> OnProgress;
public void DownloadFile()
{
Console.WriteLine("Download started...");
for (int i = 0; i <= 100; i += 10)
{
Thread.Sleep(200);
OnProgress?.Invoke(i);
}
Console.WriteLine("Download finished.");
}
}
تنها تغییری که ما ایجاد کردیم، افزودن کلمهی کلیدی event بود. کد داخلی کلاس ناشر دقیقاً
مانند
قبل است. کد مشترکین نیز برای ثبتنام تغییری نمیکند:
Program.cs
FileDownloader downloader = new();
downloader.OnProgress += (percent) => Console.WriteLine($"Progress: {percent}%");
اما اکنون، کدهای خارجی دیگر نمیتوانند کارهای خطرناک زیر را انجام دهند:
Program.cs
کلمهی کلیدی event با موفقیت از نمایندهی ما محافظت کرده و تضمین میکند که فقط خود کلاس
FileDownloader کنترل کامل بر روی زمان و چگونگی ارسال اعلانها را دارد.
الگوی استاندارد رویداد در .NET
هرچند استفاده از Action<T> برای رویدادها کاملاً کار میکند،
اما فریمورک .NET یک
الگوی استاندارد و رایج برای تعریف رویدادها دارد که استفاده از آن قویاً توصیه میشود. این
الگو قابلیت همکاری بهتری با دیگر اجزای فریمورک را فراهم میکند.
این الگو بر پایهی نمایندهی جنریک System.EventHandler<TEventArgs> است. امضای این نماینده به صورت
زیر است:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
- `sender`: شیئی است که رویداد را برانگیخته است (همان ناشر).
- `e`: یک شیء است که دادههای مربوط به رویداد را در خود حمل میکند. این شیء
باید از کلاس پایهی EventArgs ارثبری کند.
برای پیادهسازی این الگو، ابتدا یک کلاس سفارشی برای دادههای رویداد خود میسازیم:
Program.cs
public class ProgressEventArgs : EventArgs
{
public int Percentage { get; }
public ProgressEventArgs(int percentage) { Percentage = percentage; }
}
public class StandardFileDownloader
{
public event EventHandler<ProgressEventArgs> OnProgress;
public void DownloadFile()
{
for (int i = 0; i <= 100; i += 25)
{
OnProgress?.Invoke(this, new ProgressEventArgs(i));
}
}
}
استفاده از این الگو باعث میشود کد شما برای دیگر برنامهنویسان C# بسیار آشناتر و قابل
فهمتر باشد.