CodeGym /Kurslar /C# SELF /Abunəliyin detallı izahı ( +=)

Abunəliyin detallı izahı ( +=)

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

C#-dakı eventlər sadəcə delegat yazılan dəyişənlər deyil. Bu, qorunan handler siyahısıdır və yalnız event-in sahibi onun icrasını başlada bilər; digərləri yalnız öz reaksiyalarını əlavə edə (+=) və ya götürə (-=) bilərlər.

Məsələn, belə abunə olmaq olar:

worker.WorkCompleted += Worker_WorkCompleted;

Birinci baxışda bu adi toplama kimi görünə bilər, amma əslində fərqlidir. Alt tərəfdə event invocation list saxlayır — çağırılmalı delegatların dəsti. Siz += yazanda siyahıya yeni handler əlavə olunur.

Gəlin daha ətraflı baxaq, içəridə nə baş verir, necə zəncir formalaşır və hansı nüanslar ola bilər.

Delegat-zəncir nədir?

Xatırladaq ki, C#-da delegatlar "multicast"dır: onlara bir neçə metod təyin etmək olar və delegat çağırılarsa hamısı ardıcıl icra olunacaq. Eventlər bu mexanizmdən istifadə edir: onların dəyəri əslində handler siyahısına sahib delegatdır.

Kod baxımından:

public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

Kiminsə abunə olması:

worker.WorkCompleted += MyHandler;

C# alt tərəfdə təxminən belə edir:

  • Mövcud delegatı (handler siyahısını) götürür.
  • Onun üçün Delegate.Combine metodunu çağırır (handler-ları birləşdirir).
  • Yenilənmiş zənciri event dəyişəninə yazır.

Şematik:

Əməliyyat Event-in daxili handler siyahısı
Əvvəl null və ya [Handler1]
+= sonra Handler2 [Handler1, Handler2]
Yenə += ilə H3 [Handler1, Handler2, Handler3]

Maraqlı fakt: multicast-delegat mexanizmi "sehr" deyil, konkret implementasiyadır: delegatın altında çağırılacaq metodların massivinə oxşar struktur var.

2. Abunə necə işləyir: sadə izah

Gəlin konkret nümunəyə baxaq. Tutaq bizdə publisher (Worker) və subscriber-lər (Logger, Notifier) var:

public class Worker
{
    public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

    public void DoWork()
    {
        // ... iş ...
        OnWorkCompleted("Tapşırıq tamamlandı!");
    }

    protected virtual void OnWorkCompleted(string message)
    {
        WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
    }
}

public class Logger
{
    public void LogWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Log: " + e.Message);
    }
}

public class Notifier
{
    public void ShowNotification(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Bildiriş: " + e.Message);
    }
}

Main-də:

var worker = new Worker();
var logger = new Logger();
var notifier = new Notifier();

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += notifier.ShowNotification;

// İşin başladılması
worker.DoWork();

OnWorkCompleted çağırılarkən əvvəlcə logger.LogWorkCompleted, sonra notifier.ShowNotification çağırılacaq (abunə olunduğunuz sıraya görə).

3. Faydalı nüanslar

Vizual: event handler-ları necə saxlayır

+---------------------+
| Worker              |
|---------------------|
| WorkCompleted Event |    ---> [ LogWorkCompleted, ShowNotification ]
+---------------------+

Abunə olunanda hər yeni handler mövcud siyahıya "əlava olunur". Event çağırıldıqda delegat sırayla bütün metodları icra edir.

Bir metodla təkrarlı abunə

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += logger.LogWorkCompleted; // İki dəfə!

Belə olduqda handler abunə olunduğu sayda çağırılacaq — burada iki dəfə ardıcıl.

Lambda ifadəsi ilə abunə

worker.WorkCompleted += (sender, e) => Console.WriteLine("Anonim handler: " + e.Message);

Əgər bu lambdanı bir neçə dəfə abunə etsəniz — eyni qaydada hər dəfə çağırılacaq. Amma diqqət: hər lambda öz delegat obyektinə malikdir, ona görə asanlıqla unsubscribe edə bilməyəcəksiniz (daha ətraflı 260-cı mühazirədə və sonrasında).

Abunənin içində necə işləyir: aşağı səviyyəli analiz

Event xüsusi property-dir, iki accessor metodu var (add/remove), hansı ki += və -= istifadə ediləndə çağırılır. Sadələşdirsək, kompilyator təxminən belə bir kod generasiya edir:

// Təxminən belə (sadələşdirilmiş)
public event EventHandler<WorkCompletedEventArgs> WorkCompleted
{
    add { /* handler əlavə etmə kodu */ }
    remove { /* handler silmə kodu */ }
}

Default implementasiya Delegate.Combine ilə birləşmə və Delegate.Remove ilə silinmədən istifadə edir.

Bu event-i qoruyur: xaricdən event-i birbaşa çağırmaq olmaz (heç bir worker.WorkCompleted(...); olmur), yalnız abunə olmaq və ya ayrılmaq mümkündür.

Abunə mexanikası: sxem

             +----------------------+
             |                      |
             v                      v
    +--------------------+   +----------------------+
    | LogWorkCompleted   |   | ShowNotification    |
    +--------------------+   +----------------------+
             ^                      ^
             \______________________/
                       ^
                       |
               WorkCompleted Event

Bu "Invocation List" adlanır — çağırış zənciri.

Niyə bu vacibdir: praktiki əhəmiyyəti

Abunə mexanizmini başa düşmək obyektlər arasındakı əlaqələri idarə etmək üçün vacibdir. Komponentlər dinamik şəkildə event-lərə qoşulub ayıra bilər, sərt bağlılıqlar yaratmadan. Bu UI framework-lərdə, oyun mühərriklərində, server tətbiqlərində və müasir mikroservis memarlığında (orada ideya eynidir, sadəcə infrastruktura görə message queue səviyyəsində olur) geniş istifadə olunur.

Müsahibələrdə tez-tez soruşurlar: C# event model necə işləyir, niyə event-lər sistemi elastik edir və abunənin life-cycle necə düzgün idarə olunur.

4. Sinifdən kənarda nə etmək olar (və olmaz)

Olur

  • Event-ə abunə olmaq (+=)
  • Abunəliyi ləğv etmək (-=)

Olmaz

  • Event-i birbaşa çağırmaq
  • Event-ə delegatı birbaşa təyin etmək (worker.WorkCompleted = ... — səhv!)

Bu məhdudiyyətlər event açar sözü ilə tətbiq olunur. Əgər delegatı adi sahə kimi elan etsəydin:

public EventHandler<WorkCompletedEventArgs> WorkCompleted; // event deyil!

— hər kəs hər şeyi edə bilərdi, o cümlədən handler-ları sıfırlamaq, bu da qarışıqlıq və potensial bug-lar yaradar. Odur ki, demək olar həmişə yalnız event istifadə etmək lazımdır!

Bir metod ilə bir neçə event-ə abunə oluna bilərmi?

Bəli! Bu "multisubscribe" adlanır. Məsələn:

worker.WorkCompleted += logger.LogWorkCompleted;
anotherWorker.WorkCompleted += logger.LogWorkCompleted;

Əgər eyni metodu müxtəlif obyektlərin eventlərinə abunə etsəniz, handler hər iki eventdə də işə düşəcək. Handler daxilində kim çağırdığını sender parametri ilə anlaya bilərsiniz.

Həqiqi case: dinamik abunələr

Tutaq tətbiqdə istifadəçi paralel bir neçə tapşırıq işə salır. Hər yeni tapşırıq üçün Worker obyekti yaradılır və eyni handler-ə — məsələn, logger.LogWorkCompleted — abunə olunur.

var allWorkers = new List<Worker>();
for (int i = 0; i < 10; i++)
{
    var w = new Worker();
    w.WorkCompleted += logger.LogWorkCompleted;
    allWorkers.Add(w);
}

Nəticədə, hər hansı bir tapşırıq bitəndə logger xəbərdar olacaq və nə vaxt nə baş verdiyini qeyd edəcək.

Praktik məqamlar: event abunəçilərini necə görmək

Adi kodda event-ə neçə handler abunə olduğunu birbaşa öyrənmək mümkün deyil (event delegatı inkapsulyasiya edir). Amma publisher sinifin daxilində event delegatı ilə işləyə bilərsiniz, məsələn, onun GetInvocationList() metodunu çağırmaq:

// Yalnız publisher sinifin daxilində
var handlers = WorkCompleted?.GetInvocationList();
if (handlers != null)
    Console.WriteLine($"Abunəçilər: {handlers.Length}");

Bu debug və xüsusi paylama loqikasında faydalı ola bilər (amma dərslik məqsədləri üçün istifadə etmək daha yaxşıdır!).

5. Tipik səhvlər və xüsusiyyətlər

Handler əlavə edilməyib? Çağırılmayacaq!
Əgər abunə olmamısınızsa, handler heç vaxt çağırılmayacaq. Event-i çağırmazdan əvvəl həmişə null yoxlaması etmək lazımdır (yoxsa NullReferenceException alarsınız).

Bir neçə dəfə abunə
Əgər eyni metoda bir neçə dəfə abunə olmusunuzsa — handler həmin say qədər çağırılacaq. Bəzən bu tələ olur: təsadüfi təkrarlanan += eyni bildirişi bir neçə dəfə çıxara bilər.

Static/instance metodlar
Həm static, həm də instance metodları abunə etmək olar. Əsas odur ki, imza düzgün olsun.

public static void StaticHandler(object? sender, WorkCompletedEventArgs e) { /* ... */ }
worker.WorkCompleted += StaticHandler;

Lambda və döngüdə abunə
Məsələn, döngüdə lambdalarla abunə olursunuzsa, lambda-nın "capture" etdiyi dəyişənlərin qeydini düzgün başa düşün. Yanlışlıqla istədiyinizdən fərqli dəyəri capture edə bilərsiniz.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Dinamik abunə və abunəçilərin baxışı
Dinamik abunə və abunəçilərin baxışı
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION