1. Giriş
Ənənəvi yeni başlayanlar soruşa bilər: "Bunu tətbiqlərdə harada görmək olar? Bütün siniflər arasındakı əlaqə hadisələr üzərindənmi qurulur, yoxsa birbaşa metod çağırışları iləmi?" Cavab belədir: hadisələr və delegatlar - bu, sehrli toz deyil ki, arxitektura üzərinə səpilir. Amma onlarsız müasir tətbiq çox vaxt "sıkı bağlı" (tightly coupled) olur, yəni bir komponentdə dəyişiklik etmək üçün çox sayda digərini yenidən yazmaq lazım gəlir. Hadisə və delegate əsaslı proqramlaşdırma isə çevik, genişlənə bilən və asan dəstəklənən sistemlər qurmağa imkan verir.
- UI proqramlaşdırma (WinForms, WPF, Xamarin, MAUI): kliklər, hover, mətn daxil etmə və digər istifadəçi hadisələrinin işlənməsi.
- Asinxron əməliyyatlar: fayl yükləmənin tamamlanması, şəbəkədən məlumatların alınması, timerlər.
- Plugin və genişlənən sistem arxitekturası: yeni modulları əsas koddan "sıkı" şəkildə daxil etmədən.
- Sinyal/mesaj göndərən sistemlər: çox sayda maraqlı obyektə baş verən hadisə haqqında xəbər vermək.
- Stavkaların dəyişməsinə nəzarət ("observer"): yeni mesajın yaranması, data modelinin dəyişməsi, interfeysin yenilənməsi.
İndi isə — işə başlayaq!
2. Arxitektura skeleti
Tutaq ki, siz kiçik bir konsol tətbiqi hazırlayırsınız — "Mini-chat" şirkət daxilində öyrənmə üçün (və ya sadəcə bacarıqları inkişaf etdirmək üçün). Bizdə var: İstifadəçi, Chat və, bəlkə, Bot-assistant. İstifadəçi mesaj göndərəndə, chat bütün qoşulmuş istifadəçilərə və botlara xəbər verməlidir — onlar ekranə mesajı çıxartsın və ya bot üçün avtomatik cavab hazırlasınlar.
Əvvəlki dərslərdə biz tətbiqin əsasını qurmuşduq. Xatırlayaq, hazırda hansı mərhələdəyik:
// İstifadəçi (abunəçi) sinfi
public class User
{
public string Name { get; }
public User(string name)
{
Name = name;
}
// Hadisə işləyicisi metodu
public void OnMessageReceived(object? sender, MessageEventArgs e)
{
Console.WriteLine($"[{Name}] yeni mesajı gördü: {e.MessageText}");
}
}
// Hadisə arqumentləri
public class MessageEventArgs : EventArgs
{
public string MessageText { get; }
public MessageEventArgs(string text) => MessageText = text;
}
// Chat sinfi (hadisənin yayımçısı)
public class ChatRoom
{
public event EventHandler<MessageEventArgs>? MessageReceived;
public void SendMessage(string text)
{
// Hadisəni yaradırıq (abunəçilərə xəbər veririk)
MessageReceived?.Invoke(this, new MessageEventArgs(text));
}
}
İstifadə nümunəsi:
var chat = new ChatRoom();
var user1 = new User("Anton");
var user2 = new User("Mariya");
chat.MessageReceived += user1.OnMessageReceived;
chat.MessageReceived += user2.OnMessageReceived;
chat.SendMessage("Hər kəsə salam! 😊");
Konsolda iki mesaj görəcəyik — hər iki istifadəçi yeni mesajdan xəbərdar olacaq.
3. Dinamik abunə və çıxış
Həyatda istifadəçi çata çıxıb və artıq mesaj almaq istəmir. Gəlin istifadəçiyə düzgün çıxış metodunu öyrədək:
// User sinfində "Çıxış" metodu əlavə edə bilərik
public void Unsubscribe(ChatRoom chat)
{
chat.MessageReceived -= OnMessageReceived;
}
Nümunəni inkişaf etdirək:
var chat = new ChatRoom();
var user1 = new User("Anton");
var user2 = new User("Mariya");
chat.MessageReceived += user1.OnMessageReceived;
chat.MessageReceived += user2.OnMessageReceived;
chat.SendMessage("Birinci xəbər");
user2.Unsubscribe(chat); // Mariya çatdan çıxır
chat.SendMessage("Mariya artıq bu mesajı görməyəcək");
Dinamik abunə və çıxış hər yerdə rast gəlinir: pəncərələr bağlanır, tablar bağlanır, müvəqqəti xidmətlər qlobal hadisələrdən çıxır. Unutmayın — çıxış etməyən abunəçi zombi olur, tətbiq isə yaddaş sızmasına səbəb olur!
4. Mesajların işlənməsi və cavabın yaradılması
İndi isə bot əlavə edək, hər mesajı cavablandıran. Tutaq ki, bot avtomatik "Salam!" deyir, əgər mesajda "bot" sözü keçirsə. Eyni zamanda çoxsaylı abunə və çoxadresli delegate nümunələrini göstərəcəyik.
public class Bot
{
public string Name { get; }
public Bot(string name) => Name = name;
public void OnMessageReceived(object? sender, MessageEventArgs e)
{
// Bot açar sözə cavab verir
if (e.MessageText.Contains("bot", StringComparison.OrdinalIgnoreCase))
{
if (sender is ChatRoom chatRoom)
{
Console.WriteLine($"[Bot {Name}]: Salam! Nə kömək edə bilərəm?");
// Cavab mesajı göndərir
chatRoom.SendMessage($"Bot {Name} sizə kömək etməyə hazırdır.");
}
}
}
}
İstifadə nümunəsi:
var chat = new ChatRoom();
var user = new User("Eugene");
var bot = new Bot("Yardımçı");
chat.MessageReceived += user.OnMessageReceived;
chat.MessageReceived += bot.OnMessageReceived;
// Eugene mesaj yazır, bot üçün trigger olur
chat.SendMessage("Salam, bot, necədir?");
Konsolda göstərəcək (sıra zəmanət olunmayıb):
[Eugene] yeni mesajı gördü: Salam, bot, necədir?
[Bot Yardımçı]: Salam! Nə kömək edə bilərəm?
[Eugene] yeni mesajı gördü: Bot Yardımçı sizə kömək etməyə hazırdır.
[Bot Yardımçı]: Salam! Nə kömək edə bilərəm?
[Eugene] yeni mesajı gördü: Bot Yardımçı sizə kömək etməyə hazırdır.
[Bot Yardımçı]: Salam! Nə kömək edə bilərəm?
...
Görənlər, potensial bir səhv? Bəli, sonsuz dövr: bot öz mesajlarına reaksiya verir (söz "bot"u yenidən görür). Bu vəziyyəti qarşısını almaq üçün sadə yoxlama əlavə edə bilərik:
public void OnMessageReceived(object? sender, MessageEventArgs e)
{
// Bot öz mesajlarına reaksiya vermir
if (e.MessageText.Contains("bot", StringComparison.OrdinalIgnoreCase) &&
!e.MessageText.Contains(Name))
{
if (sender is ChatRoom chatRoom)
{
Console.WriteLine($"[Bot {Name}]: Salam! Nə kömək edə bilərəm?");
chatRoom.SendMessage($"Bot {Name} sizə kömək etməyə hazırdır.");
}
}
}
Praktikada bu cür hallar dövrü hadisə işlənməsinin qarşısını almaq üçün yaxşı fikirdir, lambda ifadələri və ya ləğv mexanizmlərindən istifadə etməyi düşünmək olar (əvvəlki dərslərə baxın).
5. Asinxron əməliyyatlar və callbacklər
Hadisələr çox vaxt asinxron əməliyyatların tamamlanmasını bildirmək üçün istifadə olunur. Məsələn, internetdən məlumat yükləmək və ya uzun hesablama:
public class LongRunner
{
// Hadisə tamamlanma üçün
public event EventHandler<EventArgs>? Completed;
public async Task RunLongOperationAsync()
{
Console.WriteLine("Uzun əməliyyat başladı...");
await Task.Delay(2000); // Bu, uzun işin simulyasiyasıdır
Console.WriteLine("Əməliyyat tamamlandı, abunəçilərə xəbər verilir.");
Completed?.Invoke(this, EventArgs.Empty);
}
}
Müştəri kodu:
var runner = new LongRunner();
// Tamamlama hadisəsinə abunəlik
runner.Completed += (sender, e) =>
{
Console.WriteLine("Bildiriş alındı: əməliyyat tamamlandı!");
};
await runner.RunLongOperationAsync();
Bu, asinxron qarşılıqlı əlaqənin əsasını təşkil edir — hadisələr UI framework-lərdə məlumat yükləmənin tamamlanması, animasiyaların bitməsi, istifadəçi klikləri və s. üçün çox istifadə olunur.
6. Sinyal sistemləri
Başqa klassik nümunə: xəbərdarlıq sistemi (məsələn, onlayn mağazada endirim). Maraqlı olanlar bu barədə məlumat alır:
public class SaleNotifier
{
public event EventHandler<SaleEventArgs>? SaleOccurred;
public void AnnounceSale(string product, decimal newPrice)
{
SaleOccurred?.Invoke(this, new SaleEventArgs(product, newPrice));
}
}
public class SaleEventArgs : EventArgs
{
public string Product { get; }
public decimal NewPrice { get; }
public SaleEventArgs(string product, decimal price)
{
Product = product; NewPrice = price;
}
}
public class Customer
{
public string Name { get; }
public Customer(string name) => Name = name;
public void OnSale(object? sender, SaleEventArgs e)
{
Console.WriteLine($"[{Name}] bildiriş aldı: {e.Product} indi {e.NewPrice:C}!");
}
}
İstifadə nümunəsi:
var notifier = new SaleNotifier();
var c1 = new Customer("Andrey");
var c2 = new Customer("Olga");
notifier.SaleOccurred += c1.OnSale;
notifier.SaleOccurred += c2.OnSale;
notifier.AnnounceSale("Çaydanlıq", 999.99m);
Əgər bir müştəri artıq maraqlanmırsa, abunəliyi kəsirik:
notifier.SaleOccurred -= c2.OnSale;
notifier.AnnounceSale("Mikser", 1999.99m);
Bu pattern bildirişlərin yayılması (notification, pub/sub) üçün istifadə olunur və müasir arxitekturalarda çox vacibdir.
7. Hadisə zəncirinin ləğvi mexanizmi
Bəzən, bir işləyici "davam edən" hadisə yayımını dayandırmalıdır. Adətən, bunun üçün EventArgs-in törəməsini və "cancel" bayrağını istifadə edirlər:
public class CancelEventArgs : EventArgs
{
public bool Cancel { get; set; }
}
public class EventSource
{
public event EventHandler<CancelEventArgs>? SomethingHappened;
public void DoSomething()
{
var args = new CancelEventArgs();
// Abunəçiləri dövr etmək üçün klassik dövr
var handlers = SomethingHappened?.GetInvocationList();
if (handlers != null)
{
foreach (var handler in handlers)
{
((EventHandler<CancelEventArgs>)handler)(this, args);
if (args.Cancel)
{
Console.WriteLine("Hadisə zənciri dayandırıldı.");
break;
}
}
}
}
}
İstifadə nümunəsi:
var source = new EventSource();
source.SomethingHappened += (s, e) =>
{
Console.WriteLine("Birinci işləyici");
};
source.SomethingHappened += (s, e) =>
{
Console.WriteLine("İkinci işləyici hadisəni ləğv edir.");
e.Cancel = true;
};
source.SomethingHappened += (s, e) =>
{
Console.WriteLine("Bu işləyici çağırılmayacaq.");
};
source.DoSomething();
Nəticə:
Birinci işləyici
İkinci işləyici hadisəni ləğv edir.
Hadisə zənciri dayandırıldı.
Bu mexanizm, validasiya, pəncərəni bağlamadan əvvəl yoxlama, icazələrin yoxlanması və digər hallarda "Dayan! Daha irəli getmirik!" demək üçün lazımdır.
8. Thread-safety və abunəçilərin idarə olunması
Çoxmərhələli tətbiqlərdə, abunəçilər hadisə yaradılarkən eyni zamanda əlavə və silinirsə, thread-safe nümunələr istifadə etmək vacibdir (rəsmi sənədlərə baxın: rəsmi sənəd). Bu məqsədlə, hadisəni əl ilə idarə edən accessor-lər (add, remove) ilə həyata keçirmək olar:
public class CustomEvent
{
private EventHandler? _handlers;
public event EventHandler SomethingHappened
{
add
{
lock (this) // thread-safe
{
_handlers += value;
}
}
remove
{
lock (this)
{
_handlers -= value;
}
}
}
protected void RaiseEvent()
{
// Kopyalama
EventHandler? handler;
lock (this)
{
handler = _handlers;
}
handler?.Invoke(this, EventArgs.Empty);
}
}
Bu yanaşma nadir hallarda istifadə olunur (standart mexanizm kifayət edir), amma yüksək yüklü sistemlər üçün lazım ola bilər.
9. UI-də hadisələr — WinForms/WPF/MAUI
WinForms:
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Düymə basıldı!");
}
Bu metodun arxasında abunəlik mexanizmi gizlidir:
button1.Click += button1_Click;
WPF/MAUI: PropertyChanged tipli hadisələr (modelin dəyişikliklərinin bildirişi):
public class MyViewModel : INotifyPropertyChanged
{
private string _value;
public string Value
{
get => _value;
set
{
if (_value != value)
{
_value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
Frameworklər reaktiv interfeys qurmaq üçün hadisələrdən geniş istifadə edir (MVVM).
10. Tipik səhvlər və gəbələr
Hadisələri tətbiqinizə daxil edərkən, tez-tez rast gəlinən bəzi səhvlər:
- Yaddaş sızması: not properly unsubscribed abunəçilər. Xüsusilə, qısa ömürlü obyekt uzunömürlü hadisəyə abunə olursa. Standart təcrübələrə əməl edin (
Dispose()istifadə edin). - Abunəçilərin çağırış sırasına bağlılıq. Bu, çox vaxt düzgün deyil! Sıra zəmanət olunmayıb.
- Sonsuz dövr (yuxarıdakı bot nümunəsinə baxın).
- Güclü əlaqə: abunəçi və yayımlayıcı arasında. Hadisələrdən istifadə edin, əlaqəni azaldın.
- Thread-unsafe hadisə çağırışı. Delegatı kopyalayın və ya
?.Invoke()istifadə edin.
GO TO FULL VERSION