CodeGym /Kursy /C# SELF /Wyrażenia lambda dla event

Wyrażenia lambda dla event i callback

C# SELF
Poziom 50, Lekcja 3
Dostępny

1. Wprowadzenie

W C# (i w .NET w ogóle) zdarzenia — to jeden ze sposobów, w jaki obiekty komunikują się między sobą. Na przykład masz przycisk, którego kliknięcie ma coś uruchomić. Można by obsłużyć zdarzenie przez deklarację oddzielnej metody, a potem tą metodę jawnie podpisać do zdarzenia... Ale często chce się po prostu "opisać reakcję tutaj", nie rozsypując logiki po różnych częściach pliku.

Wyrażenia lambda świetnie rozwiązują ten problem — pozwalają dosłownie napisać reakcję na zdarzenie w tym samym miejscu, gdzie się na nie subskrybujesz. Minimum ceremonii — maksimum użyteczności.

To samo dotyczy callbacków: czasem przekazujesz do metody jakąś logikę do wykonania "później", np. gdy operacja się zakończy, i znowu — lambdy robią to prosto i czytelnie: cała potrzebna logika jest tuż obok, w miejscu wywołania.

Zdarzenia i callbacki: krótko o najważniejszym i gdzie przydają się lambdy

Zdarzenie — to nic innego jak "tablica ogłoszeń" na obiekcie: "tutaj ktoś może się podpisać i coś zrobić, gdy coś się wydarzy".

Callback (wywołanie zwrotne) — to "daję ci funkcję teraz, a ty ją wywołaj, kiedy będzie trzeba". Bardzo przypomina "zostaw zgłoszenie — oddzwonimy".

Kiedy poznawałeś delegaty i zdarzenia wcześniej, już widziałeś składnię mniej więcej taką:

button.Click += MyButtonClickHandler;

Gdzie MyButtonClickHandler — to specjalna metoda, którą trzeba gdzieś zadeklarować. Ale kiedy logika jest prosta, chce się obyć kilkoma liniami, nie mnożąc metod dla jednorazowego działania.

Tutaj na scenę wchodzą lambdy:

button.Click += (sender, args) => { Console.WriteLine("Przycisk kliknięty!"); };

Zwięźle, czytelnie, tutaj i teraz!

2. Lambdy jako handlery zdarzeń: od prostego do ciekawszego

Prosta forma: żarówka mrugnęła — coś wypisaliśmy w konsoli

Załóżmy, że mamy klasę Button, i chcemy reagować na jej zdarzenie Click.

// Wyobraźmy sobie taki przycisk:
public class Button
{
    public event EventHandler? Click;

    public void SimulateClick() // Dla przykładu: "klikamy" programowo
    {
        // Jeśli ktoś się podpisał — wywołujemy handlery
        Click?.Invoke(this, EventArgs.Empty);
    }
}

Teraz użyjemy tego przycisku:

var button = new Button();

// Subskrybujemy zdarzenie przycisku za pomocą lambdy!
button.Click += (sender, args) =>
{
    Console.WriteLine("Hurra! Przycisk został kliknięty!");
};

button.SimulateClick(); // => Hurra! Przycisk został kliknięty!

Wyjaśnienie:
— Wszystko, co po => — to ciało twojej "mini-funkcji", która zostanie wywołana, kiedy zdarzenie nastąpi.
— Nie musisz myśleć o cyklu życia handlera: żyje tak długo, jak twoja subskrypcja (+=). Ważne: jeśli lambda przechwytuje zmienne z zewnątrz i nie odsubskrybujesz się od zdarzenia, to może to prowadzić do wycieków pamięci.

Lambda-handler z przechwytywaniem zmiennych

int clickCount = 0;

button.Click += (sender, args) =>
{
    clickCount++;
    Console.WriteLine($"Przycisk kliknięty już {clickCount} raz(y)!");
};

button.SimulateClick(); // => Przycisk kliknięty już 1 raz(y)!
button.SimulateClick(); // => Przycisk kliknięty już 2 raz(y)!

Co się dzieje:
Lambda "przechwytuje" zmienną clickCount z zewnątrz i pamięta jej wartość między wywołaniami.

3. Zastosowanie wyrażeń lambda dla callbacków

Callback często używany jest w sytuacjach, gdy jakiś kod ma wywołać twoją funkcję później — np. po wykonaniu długotrwałej pracy.

Przekazanie lambdy jako callback

Załóżmy, że mamy metodę, która przyjmuje delegat lub lambdę jako argument:

void DoWork(Action callback)
{
    Console.WriteLine("Zaczyna się praca...");
    // Symulacja pracy
    System.Threading.Thread.Sleep(500); // (Nie rób tak w realnych aplikacjach, to operacja blokująca!) 
    callback(); // Po pracy wywołujemy callback
}

DoWork(() => Console.WriteLine("Praca zakończona!"));

Rezultat:
Najpierw w konsoli pojawi się "Zaczyna się praca...", potem "Praca zakończona!".

Można przekazywać lambdy z parametrami:

void Calculate(int a, int b, Action<int> onResult)
{
    int sum = a + b;
    onResult(sum);
}

Calculate(5, 8, result => Console.WriteLine($"Wynik: {result}"));

4. Typowe błędy przy użyciu lambd

Brak odsubskrybowania od zdarzeń

Kiedy subskrybujesz zdarzenie za pomocą lambdy, nie masz "nazwy handlera", żeby potem łatwo się odsubskrybować:

button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // Jak tu przekazać twoją lambdę? Nie da się, jeśli jej nie zapisałeś.

Jeśli naprawdę potrzebujesz się odsubskrybować — zapisz lambdę do zmiennej:

EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...potem
button.Click -= handler;

To ważne dla obiektów długożyjących: jeśli się nie odsubskrybujesz, referencja zostanie i może doprowadzić do wycieku pamięci.

Przechwytywanie zmiennych w pętli

Klasyka: subskrypcja zdarzeń wewnątrz pętli:

for (int i = 0; i < 3; i++)
    button.Click += (s, e) => Console.WriteLine(i); // Wszystkie lambdy "pamiętają" ten sam i

W nowoczesnych wersjach C# to zachowanie jest poprawione dla foreach, ale dla for czasem mogą być niuanse — zawsze bądź ostrożny z przechwytywanymi zmiennymi!

Jeśli chcesz, żeby każda lambda pamiętała swoją wartość — tworzysz lokalną kopię:

for (int i = 0; i < 3; i++)
{
    int localI = i;
    button.Click += (s, e) => Console.WriteLine(localI);
}
2
Zadanie
C# SELF, poziom 50, lekcja 3
Niedostępne
Licznik kliknięć z przechwytywaniem zmiennych
Licznik kliknięć z przechwytywaniem zmiennych
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION