CodeGym /Kursy /C# SELF /Tworzenie zdarzeń za pomocą delegatów (

Tworzenie zdarzeń za pomocą delegatów ( event i delegate)

C# SELF
Poziom 52 , Lekcja 1
Dostępny

1. Wprowadzenie

Więc kiedy mówimy o „zdarzeniu” (event) w kontekście C#, mamy na myśli mechanizm, który pozwala bezpiecznie powiadomić jeden lub kilka obiektów, że coś się wydarzyło. Zdarzenia to nie magia: pod maską to delegaty (słowo kluczowe delegate) z dodatkowymi zabezpieczeniami przed nieprawidłowym użyciem.

Wyobraź sobie subskrypcję kanału na YouTube. Kanał (obiekt-właściciel zdarzenia) ma przycisk „Subskrybuj” — każdy widz (inny obiekt) może go kliknąć i trafić na listę subskrybentów (delegaci-obsługujący). Gdy autor publikuje nowe wideo (wywołuje zdarzenie), powiadomienia trafiają tylko do subskrybentów, i tylko właściciel kanału decyduje, kiedy to nastąpi. Ważna rzecz: widzowie mogą się subskrybować lub rezygnować, ale nie mogą sami wysłać powiadomienia — to prawo ma tylko właściciel kanału.

Trochę formalnej składni


// Deklaracja delegata, który będzie określać "kształt" obsługi zdarzenia
public delegate void SimpleEventHandler();

// Deklaracja zdarzenia opartego na delegacie
public event SimpleEventHandler SomethingHappened;

Proste: event to słowo kluczowe do deklarowania zdarzenia, a typ zdarzenia jest zawsze określany przez delegata.

2. Pierwszy prosty przykład: zdarzenie „Przycisk kliknięty!”

Krok 1. Definiujemy delegata dla obsług zdarzenia


// Obsługa zdarzenia: nie zwraca nic, nie przyjmuje parametrów
public delegate void ButtonClickHandler();

Krok 2. Klasa ze zdarzeniem


public class Button
{
    // Zdarzenie: można subskrybować i rezygnować, ale nie można go wywołać z zewnątrz
    public event ButtonClickHandler Click;

    // Metoda, która "naciska przycisk"
    public void Press()
    {
        Console.WriteLine("Przycisk został naciśnięty!");
        // Wywołanie zdarzenia: powiadomić wszystkich subskrybentów
        Click?.Invoke();
    }
}

Zwróć uwagę: zdarzenie jest zadeklarowane na podstawie delegata, a w metodzie Press zdarzenie jest wywoływane przez ?.Invoke(). Dlaczego? Bo zdarzenie może być puste (nikt się nie zapisał), wtedy Click jest równe null. Operator bezpiecznego wywołania gwarantuje, że obsługi zostaną wywołane tylko jeśli są subskrybenci.

Krok 3. Subskrypcja zdarzenia i uruchomienie kodu


// Przykład użycia
public class Program
{
    public static void Main()
    {
        var button = new Button();

        // Dodajemy "słuchacza" (subskrybujemy zdarzenie)
        button.Click += OnButtonClicked;
        button.Click += () => Console.WriteLine("Jeszcze jeden handler!");

        button.Press(); // Symulujemy naciśnięcie przycisku

        // Można się wypisać ze zdarzenia
        button.Click -= OnButtonClicked;
        button.Press();
    }

    // Zwykły handler zdarzenia
    public static void OnButtonClicked()
    {
        Console.WriteLine("Handler: Przycisk został naciśnięty!");
    }
}

Przy pierwszym naciśnięciu zadziałają oba handlery, przy drugim — tylko lambda.

3. Różnice między zdarzeniem a delegatem

Do delegata można swobodnie przypisywać wartości i nawet całkowicie zastąpić listę subskrybentów — ktoś może napisać coś w stylu button.Click = null i wszystkie wcześniejsze subskrypcje „znikną”.

Ze zdarzeniem jest inaczej — jest ściślej związane z obiektem. Tylko właściciel klasy, w której zdarzenie zostało zadeklarowane, może je wywołać bezpośrednio (na przykład Click() z wnętrza klasy). Inny kod może tylko subskrybować przez += lub rezygnować przez -=, ale nie może „wyzerować” wszystkiego naraz. To chroni enkapsulację i uniemożliwia „zepsucie” systemu subskrypcji.

4. Sygnatura: typy delegatów dla zdarzeń, parametry

Tradycja w .NET jest taka: handler zdarzenia dostaje dwa parametry — object sender (kto wywołał zdarzenie) i argumenty zdarzenia (EventArgs). Zaleca się używać delegatów EventHandler lub EventHandler<TEventArgs>.

Przykład: delegat z parametrami


public delegate void ButtonClickHandler(object sender, EventArgs e);

EventArgs — klasa bazowa do przekazywania dodatkowych informacji. Jeśli potrzebujesz więcej danych, tworzysz własne dziedziczenie.

Zastosujmy to do naszego przycisku


// Klasa argumentów zdarzenia
public class ButtonClickEventArgs : EventArgs
{
    public string UserName { get; }

    public ButtonClickEventArgs(string userName)
    {
        UserName = userName;
    }
}

public class Button
{
    public event EventHandler<ButtonClickEventArgs> Click;

    public void Press(string userName)
    {
        Console.WriteLine("Przycisk został naciśnięty!");
        Click?.Invoke(this, new ButtonClickEventArgs(userName));
    }
}

public static void Main()
{
    var button = new Button();
    button.Click += OnButtonClicked;

    button.Press("Wasilij");
}

public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
    Console.WriteLine($"Użytkownik {e.UserName} kliknął przycisk!");
}

5. Schemat wizualny: jak działa zdarzenie


+-------------+
| Użytkownik  |
+-------------+
      |
      v
+--------------+
|  Button.Press|
+--------------+
      |
      v
+-----------------+         +------------------------+
|  Wywołanie Click? |----->---|   Subskrybent 1       |
+-----------------+         +------------------------+
      |                     |   Wykonaj handler       |
      v                     +------------------------+
+-----------------+         +------------------------+
|   Jeśli subskrybent|----->---|   Subskrybent 2     |
+-----------------+         +------------------------+
      :                     |   Wykonaj handler       |
      v                     +------------------------+

Button.Press wywołuje zdarzenie; handlery subskrybentów wykonują się po kolei.

6. Przydatne niuanse

Rekomendowany kształt zdarzeń w .NET

Używaj EventHandler i EventHandler<TEventArgs>, żeby kod był kompatybilny z bibliotekami i narzędziami. Takie podejście ułatwia ewolucję zdarzeń: dodajesz nowe właściwości do swoich EventArgs bez łamania subskrybentów.

Dokumentacja: EventHandler, event.

Zdarzenia vs delegaty

Jeśli chcesz powiązać jeden komponent z jedną konkretną metodą — wystarczy delegat (delegate). Jeśli zaś potrzebujesz, żeby różne części programu mogły się subskrybować i rezygnować w dowolnym momencie — użyj zdarzenia (event).

7. Typowe błędy i niezręczności przy tworzeniu zdarzeń

Błąd nr 1: wywołanie zdarzenia bez sprawdzenia na null. Jeśli zdarzenie nie ma subskrybentów, bezpośrednia próba jego wywołania spowoduje NullReferenceException. Używaj bezpiecznego wywołania:


Click?.Invoke(...);

Błąd nr 2: próba wywołać zdarzenie z zewnątrz klasy. Zdarzenie można podnieść (wywołać) tylko wewnątrz tej klasy, w której jest zadeklarowane. Z innej klasy kompilator zgłosi błąd — to chroni enkapsulację.

Błąd nr 3: wielokrotna subskrypcja tego samego handlera. Jeśli ten sam handler zostanie subskrybowany wiele razy, zostanie wywołany tyle razy. To cecha mechanizmu subskrypcji. Uważaj na duplikaty += i poprawne używanie -=.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION