CodeGym /Kursy /C# SELF /Wzorzec „Wydawca‑Subskrybent” i zdarzenia w C# (

Wzorzec „Wydawca‑Subskrybent” i zdarzenia w C# ( event)

C# SELF
Poziom 52 , Lekcja 0
Dostępny

1. Wprowadzenie

Pracując z programami, nieuchronnie napotykasz sytuacje, gdy jedna część musi „powiadomić” inne, że wydarzyło się coś ważnego. Klasyczny przykład — użytkownik kliknął myszką i to zdarzenie wymaga obsługi. W codziennym życiu żyjemy już w świecie zdarzeń: czajnik na kuchence zapisał — usłyszałeś sygnał i pospiesznie zgasiłeś palnik. Kawa rozlała się na klawiaturę — serce zadrżało — i rzuciłeś się ratować laptopa. Programowanie rządzi się tymi samymi prawami.

Zdarzenie — to mechanizm pozwalający obiektowi‑źródłu (wydawcy) powiadomić inne obiekty (subskrybentów) o zajściu zmian lub akcji. To coś w stylu „powiadomiłem — kto słyszał, ten zareagował”.

W C# zdarzenia — specjalna konstrukcja oparta na typie delegata. Delegat określa sygnaturę callbacka — co i jak zostanie wywołane u subskrybentów. Deklaracja zdarzenia odbywa się słowem kluczowym event, a delegat — słowem kluczowym delegate.

Po co zdarzenia?

  • Luźne powiązanie: Wydawca nic nie wie o subskrybentach — jedynie wysyła sygnał.
  • Elastyczność architektury: Można dynamicznie dodawać i usuwać handlery, nie zmieniając kodu wydawcy.
  • Skalowalność: Dodano nowego subskrybenta — i od razu zaczyna otrzymywać powiadomienia.

Klasyczny wzorzec „Wydawca‑Subskrybent”

Wyobraźmy sobie, że mamy klasę „Alarm przeciwpożarowy” (wydawca) i klasę „Człowiek w budynku” (subskrybent). Kiedy alarm zadziała, wysyła sygnał do wszystkich naraz — bez względu na to, ile osób jest w budynku i gdzie dokładnie. To jest wzorzec „Wydawca‑Subskrybent” (czyli Observer).

Wydawca nie wie, ile i jakich subskrybentów jest — po prostu powiadamia, a reszta sama subskrybuje powiadomienia albo je ignoruje.

Jak to działa w C#?

  • Wydawca: definiuje zdarzenie (event), udostępnia subskrypcję/odsubskrypcję.
  • Subskrybent: subskrybuje zdarzenie i implementuje handler (metoda‑handler zostanie wywołana przy wystąpieniu zdarzenia).

2. Zdarzenia w praktyce: pierwszy przykład

Przechodząc od domu do kodu, opiszmy najprostszą modelkę. Załóżmy, że mamy aplikację konsolową, gdzie obiekt‑timer co sekundę „tyka”, a różni handlerzy reagują (np. wypisują „tik” w konsoli albo liczą liczbę tików).

Krok 1. Definiujemy delegata i zdarzenie


public class SimpleTimer
{
    // Zadeklarujmy delegata dla zdarzenia
    public delegate void TickEventHandler(object sender, EventArgs e);

    // Zdarzenie oparte na delegacie
    public event TickEventHandler Tick;

    public void Start(int count)
    {
        for (int i = 0; i < count; i++)
        {
            System.Threading.Thread.Sleep(1000); // symulacja tykania!
            OnTick(); // wyrzuć zdarzenie!
        }
    }

    protected virtual void OnTick()
    {
        // Wywołaj zdarzenie, jeśli są subskrybenci (Tick != null)
        Tick?.Invoke(this, EventArgs.Empty);
    }
}

Co tu się dzieje?

  • Zdefiniowano delegata TickEventHandler z klasyczną sygnaturą object sender, EventArgs e.
  • Zdarzenie Tick — punkt subskrypcji dla handlerów.
  • Metoda Start symuluje „tykanie” i po timerze wywołuje OnTick.
  • W OnTick zdarzenie wywoływane jest bezpiecznie: Tick?.Invoke(..., EventArgs.Empty).

Krok 2. Subskrypcja zdarzenia


class Program
{
    static void Main()
    {
        var timer = new SimpleTimer();

        // Subskrybujemy zdarzenie Tick
        timer.Tick += Timer_Tick;

        timer.Start(3);

        // Można się odsubskrybować, jeśli trzeba
        timer.Tick -= Timer_Tick;
    }

    static void Timer_Tick(object sender, EventArgs e)
    {
        Console.WriteLine("Tik!");
    }
}

Tworzymy timer, subskrybujemy operatorem +=, potem przy każdym tiku wywoływany jest handler. Odsłonięcie — operator -=.

3. Przydatne niuanse

Dlaczego zdarzenia są lepsze niż „twarde” wywołania?

Gdyby SimpleTimer w OnTick pisał bezpośrednio do konsoli, klasa byłaby ściśle związana z konkretną akcją. Zdarzenia „uwalniają” kod: timer nie wie, co dokładnie zrobią subskrybenci — odpali rakietę, zaloguje do pliku czy wyśle e‑mail.

Ważna różnica między zdarzeniami a delegatami

  • Delegat — „wskaźnik” na metodę, a zdarzenie — to delegat z ograniczeniami dostępu.
  • Subskrybenci mogą tylko subskrybować/odsubskrybować; wywołać zdarzenie z zewnątrz nie można — może to zrobić tylko sam wydawca.
  • Aby zadeklarować zdarzenie, dodaj modyfikator event do typu delegata — kompilator zapewni poprawny model dostępu.

Skrócona schemat pracy zdarzenia w C#


+------------------+          +------------------------------+
|                  |          |                              |
|     Wydawca      | <------> |         Subskrybent          |
| (Publisher/Event)|          |      (Subscriber/Handler)    |
|                  |          |                              |
+------------------+          +------------------------------+
         | 1) deklaruje zdarzenie      | 2) subskrybuje je
         | 3) wywołuje je              | 4) implementuje handler

Kiedy używać zdarzeń?

  • Trzeba powiadomić nieokreśloną liczbę słuchaczy o zajściu.
  • Nie chcesz wiązać logiki akcji wewnątrz klasy‑źródła.
  • UI, interakcje asynchroniczne, systemowe powiadomienia — wszystko kręci się wokół zdarzeń.

Skrócone cechy implementacji zdarzeń w C#

  • Zdarzenia nie można wywołać „z zewnątrz”: tylko kod wydawcy ma prawo do Invoke.
  • Subskrypcja/odsubskrypcja: operatory +=/-=; handlerów może być kilka.
  • Zdarzenie to w praktyce lista delegatów: przy wystąpieniu zdarzenia wywoływane są wszystkie handlery w kolejności subskrypcji.
  • Polecane delegaty: używaj EventHandler i EventHandler<TEventArgs> dla kompatybilności z ekosystemem .NET.

4. Typowe błędy początkujących

Zapominają sprawdzić, czy są subskrybenci: Tick != null. Lepiej używać bezpiecznego wywołania: Tick?.Invoke(...).

Subskrybują zdarzenie, ale nie odsubskrybowują, gdy handler już nie jest potrzebny. To może utrzymywać obiekty w pamięci i prowadzić do wycieków.

Próbują „wywołać” zdarzenie z zewnętrznej klasy — kompilator na to nie pozwoli. Nie możesz napisać czegoś w stylu game.GameOver(), jeśli to zdarzenie, a nie metoda.

Nie przestrzegają sygnatury delegata. Dla zdarzeń używaj standardowych EventHandler lub EventHandler<TEventArgs> — wtedy kod będzie kompatybilny z innymi bibliotekami .NET.

2
Zadanie
C# SELF, poziom 52, lekcja 0
Niedostępne
Tworzenie zdarzenia i subskrybenta
Tworzenie zdarzenia i subskrybenta
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION