1. Giriş
Proqramlarla işləyərkən mütləq hallarla qarşılaşırsınız ki, bir hissə digər hissələri nəyinsə baş verdiyi barədə “xəbərdar” etməlidir. Klassik nümunə — istifadəçi mausla kliklədi və bu hadisə emal tələb edir. Gündəlik həyatda da biz hadisələr dünyasında yaşayırıq: mətbəxdə çaydanşı ulayanda — siqnalı eşidib sobanı söndürməyə tələsirsiniz. Qəhvə klaviaturaya töküldü — ürək hopdu — və siz noutbuku xilas etməyə qaçırsınız. Proqramlaşdırma da eyni qaydalara uyğundur.
Hadisə — bu, mənbə-obyektin (publisher) baş vermiş dəyişikliklər və ya əməliyyatlar barədə digər obyektləri (subscriber) xəbərdar etməsinə imkan verən mexanizmdir. Bir növ “mən elan etdim — kim diqqət yetirdi, o reaksiya verdi”.
C#-də hadisələr delegat tipi əsasında xüsusi konstruksiya olaraq mövcuddur. Delegat callback-ın imzasını təyin edir — abunəçilərdə nə və necə çağırılacaq. Hadisəni elan etmək üçün event açar sözü, delegat üçün isə delegate açar sözü istifadə olunur.
Niyə hadisələr lazımdır?
- Zəif bağlılıq: Publisher abunəçilər barədə heç nə bilmir — yalnız siqnal verir.
- Arxitektura elastikliyi: Handlerləri dinamik əlavə edib silmək olur, publisher-in kodunu dəyişməyə ehtiyac yoxdur.
- Miqyaslana bilir: Yeni subscriber əlavə elədin — dərhal bildirişlər alacaq.
Klassik “Publisher-Subscriber” şablonu
Təsəvvür edək ki, bizim “Yanğın siqnalı” (publisher) sinfi və “Binadakı insan” (subscriber) sinfi var. Siqnal işə düşəndə o, hamıya eyni anda xəbər verir — binada neçə nəfər və harada olduqları vacib deyil. Bu, Publisher-Subscriber (və ya Observer) şablonudur.
Publisher neçə və hansı abunəçilərin olduğunu bilmir — o sadəcə xəbər verir, digərləri isə özləri abunə olurlar və ya görməzdən gəlirlər.
C#-də bu necə işləyir?
- Publisher: hadisəni (event) müəyyən edir, abunə/abunəliyin ləğvi imkanını təmin edir.
- Subscriber: hadisəyə abunə olur və handler-i reallaşdırır (hadisə baş verəndə bu metod çağırılacaq).
2. Hadisələr praktikada: ilk nümunə
Kod tərəfə keçəndə sadə modeli təsvir edək. Məsələn, konsol tətbiqimiz var, burada timer obyekt hər saniyə “tik” edir və müxtəlif handler-lər reaksiya verir (məsələn, “tik”i konsola yazırlar və ya tik sayını hesablayırlar).
Addım 1. Delegat və hadisəni müəyyən edək
public class SimpleTimer
{
// Hadisə üçün delegat elan edək
public delegate void TickEventHandler(object sender, EventArgs e);
// Delegata əsaslanan hadisə
public event TickEventHandler Tick;
public void Start(int count)
{
for (int i = 0; i < count; i++)
{
System.Threading.Thread.Sleep(1000); // tiki simulyasiya edirik!
OnTick(); // hadisəni yandırırıq!
}
}
protected virtual void OnTick()
{
// Hadisənin abunəçiləri varsa çağırırıq (Tick != null)
Tick?.Invoke(this, EventArgs.Empty);
}
}
Burada nə baş verir?
- TickEventHandler adlı delegat klasik imza ilə müəyyən edilib: object sender, EventArgs e.
- Tick hadisəsi — handler-lərin abunə nöqtəsidir.
- Start metodu “tik”ləməni simulə edir və periodik olaraq OnTick-i çağırır.
- OnTick-də hadisə təhlükəsiz şəkildə çağırılır: Tick?.Invoke(..., EventArgs.Empty).
Addım 2. Hadisəyə abunə olmaq
class Program
{
static void Main()
{
var timer = new SimpleTimer();
// Tick hadisəsinə abunə oluruq
timer.Tick += Timer_Tick;
timer.Start(3);
// Lazımdırsa abunəlikdən çıxa bilərik
timer.Tick -= Timer_Tick;
}
static void Timer_Tick(object sender, EventArgs e)
{
Console.WriteLine("Tik!");
}
}
Timer yaradırıq, += operatoru ilə abunə oluruq, sonra hər tikdə handler çağırılır. Abunəliyin ləğvi — -= operatoru.
3. Faydalı nüanslar
Niyə hadisələr “sərt” çağırışlardan daha yaxşıdır?
Əgər SimpleTimer OnTick-də birbaşa konsola yazsaydı, sinf konkret əməliyyatla sərt bağlı olardı. Hadisələr isə kodu “azad edir”: timer abunəçilərin nə edəcəyini bilmir — raketi işə salacaqlar, fayla log yazacaqlar yoxsa e-mail göndərəcəklər, bu onun işinə qarışmır.
Hadisə və delegat arasındakı vacib fərq
- Delegat metodun “göstəricisi”dir, hadisə isə məhdudiyyətləri olan delegatdır.
- Subscriber-lər yalnız abunə ola və ya abunəliyi ləğv edə bilərlər; xaricdən hadisəni çağırmaq olmaz — bunu yalnız publisher özü edə bilər.
- Hadisəni elan etmək üçün delegat tipinə event modifikatorunu əlavə edin — compiler düzgün erişim modelini təmin edəcək.
C#-də hadisənin işləmə sxemi qısa
+------------------+ +------------------------------+
| | | |
| Publisher | <------> | Subscriber |
| (Publisher/Event)| | (Subscriber/Handler) |
| | | |
+------------------+ +------------------------------+
| 1) hadisəni elan edir | 2) ona abunə olur
| 3) onu çağırır | 4) handler-i reallaşdırır
Hadisələri nə vaxt istifadə etmək lazımdır?
- Məlumatı baş vermişdən sonra naməlum sayda dinləyiciyə çatdırmaq lazımdır.
- Source sinfinin daxilindəki əməliyyatları sıx bağlı etmək istəmirsinizsə.
- UI, asynchronous qarşılıqlı əlaqə, sistem bildirişləri — hamısı hadisələr ətrafında qurulur.
C#-də hadisələrin tətbiqi barədə qısa xüsusiyyətlər
- Hadisəni xaricdən çağırmaq olmaz: yalnız publisher kodu Invoke edə bilər.
- Abunə/abunəliyin ləğvi: operatorlar +=/-=; bir neçə handler ola bilər.
- Hadisə əslində delegatlar siyahısıdır: hadisə baş verəndə bütün handler-lər abunəlik sırasına görə çağırılır.
- Tövsiyə olunan delegatlar: uyğunluq üçün .NET ekosistemi ilə işləyişdə EventHandler və EventHandler<TEventArgs> istifadə edin.
4. Yeni başlayanların tipik səhvləri
Abunəçilərin olub-olmadığını yoxlamağı unuturlar: Tick != null. Təhlükəsiz çağırış üçün Tick?.Invoke(...) istifadə etmək daha yaxşıdır.
Hadisəyə abunə olurlar, amma handler artıq lazım olmadığı halda abunəliyi ləğv etmirlər. Bu obyektləri yaddaşda saxlayıb memory leak-ə gətirib çıxara bilər.
Xarici sinfdən hadisəni “çağırmağa” çalışırlar — compiler buna icazə verməyəcək. Hadisənin metod olmadığını, ona bənzər çağırış yazmağın mümkün olmadığını unutmayın.
Delegatın imzasına riayət etmirlər. Hadisələr üçün standart EventHandler və ya EventHandler<TEventArgs> istifadə edin — beləliklə kod .NET kitabxanaları ilə uyğun olacaq.
GO TO FULL VERSION