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);
}
GO TO FULL VERSION