CodeGym /Kursy /C# SELF /Standardowy szablon zdarzeń (

Standardowy szablon zdarzeń ( EventHandler/ EventArgs)

C# SELF
Poziom 52 , Lekcja 3
Dostępny

1. Wprowadzenie

Przypomnijmy: zdarzenie — to publiczny kontrakt, obietnica twojego kodu, że "zawoła" subskrybentów, kiedy coś ważnego się stanie. W przykładach z poprzednich wykładów mogłeś natknąć się na takie deklaracje:


public event Action<string> MessageSent;

Albo nawet:


public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;

To działa, ale takie podejście rodzi całą kolekcję problemów — zaczynając od rozbieżności w sygnaturze metod po niemożność dowiedzenia się, od kogo przyszło zdarzenie i co dokładnie się stało. Wyobraź sobie, że na klawiaturze przy każdym naciśnięciu klawisz działałby losowo, za każdym razem — inaczej, i to w tym samym programie do tego samego zadania. Powiedzmy, grasz w grę i naciskasz spację. Chwilę temu oznaczała skok, teraz niespodziewanie otwiera ekwipunek. Koszmar, nie standard!

W .NET jest coś w rodzaju Konstytucji dla zdarzeń — to konwencja dotycząca sygnatury handlera i struktury informacji przekazywanej ze zdarzeniem. Oto jak wygląda kanoniczny styl:


void Handler(object sender, EventArgs args);

Znana linijka? Pojawia się wszędzie: od kliknięć przycisku WinForms po systemowe zdarzenia ASP.NET i nawet w zewnętrznych bibliotekach.

2. Czym są EventHandler i EventArgs

Główna idea: każde zdarzenie przekazuje dwie rzeczy:

  • Kto wywołał zdarzenie? sender
  • Co się stało? EventArgs — dodatkowe dane

W świecie C# to wygląda tak:


public delegate void EventHandler(object sender, EventArgs e);
  • object sender — referencja do inicjatora zdarzenia. To może być cokolwiek — po prostu this.
  • EventArgs e — obiekt z dodatkowymi informacjami o zdarzeniu. Dla prostych scenariuszy używa się EventArgs.Empty, dla bardziej złożonych — własnych pochodnych EventArgs.

Ciekawostka

W oficjalnych zaleceniach .NET dla publicznych API przyjęte jest, żeby zdarzenie miało sygnaturę (object sender, EventArgs e). Jeśli widzisz zdarzenie bez sender i EventArgs — to najpewniej autorskie uproszczenie, a nie kanoniczny .NET-owy styl.

Ogromna korzyść jednego standardu

Dzięki jednolitemu standardowi zdarzenia łatwiej logować, testować, uniwersalnie się podpinać i rozszerzać system. Otwórz źródła WinForms, WPF, ASP.NET — zobaczysz ten sam wzorzec.

3. Jak używać standardowego szablonu zdarzeń

1. Używamy wbudowanego delegata EventHandler

Zamiast deklarować własny delegat można użyć gotowego:


public event EventHandler SmthHappened;

I teraz handler zawsze wygląda tak samo:


private void OnSmthHappened(object sender, EventArgs e)
{
    // Logika reakcji na zdarzenie
}

Subskrypcja nadal jest prosta:


myObj.SmthHappened += OnSmthHappened;

Ważne: jeśli zdarzenie nie przekazuje dodatkowych danych, używaj EventArgs.Empty.

2. Tworzenie własnych argumentów zdarzeń

Jeśli trzeba przekazać informację (wynik obliczeń, nazwę pliku, błąd), stwórz pochodną od EventArgs:


public class CalculationEventArgs : EventArgs
{
    public double Result { get; }
    public CalculationEventArgs(double result) => Result = result;
}

Następnie używamy uniwersalnego delegata generycznego — EventHandler<TEventArgs>:


public event EventHandler<CalculationEventArgs> CalculationFinished;

I handler teraz przyjmuje dokładnie twój typ argumentów:


private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
    Console.WriteLine($"Obliczenie zakończone. Wynik: {e.Result}");
}

4. Przykład aplikacji

Rozwijamy nasz edukacyjny projekt — niech będzie kalkulator, który dodaje lub odejmuje liczby i informuje o zakończeniu operacji przez zdarzenie.

Minimalistyczny przykład


public class Calculator
{
    public event EventHandler<CalculationEventArgs> CalculationPerformed;

    public void Add(int a, int b)
    {
        int result = a + b;
        // "Wystrzeliwujemy" zdarzenie
        CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
    }
}

public class CalculationEventArgs : EventArgs
{
    public int Result { get; }
    public CalculationEventArgs(int result) => Result = result;
}

Subskrybujemy i używamy:


var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
    Console.WriteLine($"Wynik operacji: {e.Result}");
};
calc.Add(10, 20);
// Wypisze: Wynik operacji: 30

To cały "standard": handler zawsze przyjmuje obiekt-nadawcę i obiekt argumentów — uniwersalnie i czytelnie.

5. Przydatne niuanse

Enkapsulacja wywołania zdarzenia: dobry styl

W .NET przyjęte jest wydzielanie logiki wywołania zdarzenia do osobnej chronionej metody z prefiksem On:


protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
    CalculationPerformed?.Invoke(this, e);
}

A w logice ("Add", "Subtract" itd.) po prostu wywołujemy tę metodę:


public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));

Taki styl pozwala potomkom nadpisywać zachowanie zdarzenia i zmniejsza szansę zapomnienia o jego wywołaniu.

Schemat: Jak zbudowane jest zdarzenie według standardu .NET

graph LR
    A[Obiekt-wydawca] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[Lista subskrybentów]
    B -- "Metoda-handler (object sender, EventArgs e)" --> C[Reakcja na zdarzenie]
    A -- "this (nadawca)" --> C
    A -- "EventArgs (dane)" --> C

Porównanie wariantów deklarowania zdarzeń

Podejście Przekazanie inicjatora (sender) Przekazanie argumentów Uniwersalność Użycie w .NET
public event Action<int> MyEvent;
Nie Tak Niska Nie
public delegate void MyHandler(object, int); event MyHandler ...
Tak Tak Średnia Rzadko
public event EventHandler;
Tak Nie (EventArgs) Wysoka Tak, standard
public event EventHandler<MyArgs>;
Tak Tak (MyArgs) Bardzo wysoka Tak, standard

Praktyczna korzyść na rozmowach rekrutacyjnych i w produkcyjnym kodzie

Używanie standardowego szablonu zdarzeń — must-have dla .NET-developera. Na rozmowie prawie na pewno zapytają cię o EventHandler i parę sender/EventArgs. Zdarzenie typu Action<T> często jest postrzegane jako "uproszczenie".

W realnych projektach takie podejście ułatwia współpracę, testowanie, rozszerzalność i utrzymanie kodu. Zewnętrzne biblioteki (logowanie, profilery) łatwiej się integrują, kiedy używany jest standardowy format.

Schemat blokowy wywołania zdarzenia

flowchart TD
    subgraph A[Klasa-wydawca]
        C1((Obiekt))
        C2[Metoda wywołująca zdarzenie]
        C3["event EventHandler<MyArgs>"]
    end
    subgraph B[Klasa-subskrybent]
        D1((Subskrypcja))
        D2["Handler zdarzenia (object sender, MyArgs args)"]
    end
    C1 -- Wywołuje --> C2
    C2 -- "Invoke(this, args)" --> C3
    C3 -- "Powiadamia" --> D1
    D1 -- "Wywołuje" --> D2

6. Porady, niuanse i typowe błędy

1. Sygnatura handlera. Chcesz zrobić zdarzenie typu Action<int>? To kuszące, ale tracisz sender i kompatybilność z ekosystemem.

2. Przekazywanie argumentów. Nie myl EventArgs z zwykłymi parametrami. Wszystkie dane dla handlera przekazuj w obiekcie argumentów.

3. Używanie null dla argumentów. Zamiast null używaj EventArgs.Empty, jeśli nie ma dodatkowych danych.

4. Słaba typizacja. Nie rób jednego "wszystkożernego" zdarzenia EventHandler i nie wrzucaj tam wszystkiego. Stwórz oddzielną klasę-pochodną dla każdego zdarzenia — czytelność i niezawodność rosną.

5. Błędy przy wywoływaniu zdarzenia. Zawsze sprawdzaj obecność subskrybentów: SomeEvent?.Invoke(this, e). Bez subskrybentów referencja zdarzenia jest równa null.

6. Naruszenie enkapsulacji. Nie wywołuj zdarzenia z zewnątrz klasy-wydawcy. Zdarzenie — tylko do subskrypcji/odsubskrypcji; wywołuj je wewnątrz przez metodę On....

2
Zadanie
C# SELF, poziom 52, lekcja 3
Niedostępne
Tworzenie zdarzenia z EventHandler i własnym EventArgs
Tworzenie zdarzenia z EventHandler i własnym EventArgs
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION