1. Wprowadzenie
Możesz o tym nie wiedzieć, ale jedno z ulubionych pytań na rozmowie kwalifikacyjnej to „Co wiesz o wyrażeniach lambda?”. Dlaczego o to pytają? Bo to bardzo wygodna i "modna" rzecz w C#, pozwalająca pisać mniej kodu i robić go bardziej wyrazistym. Jeśli metody anonimowe to jak pisać list ręcznie, to wyrażenia lambda to jak wysyłać wiadomość w komunikatorze: szybko, kompaktowo i czasem nawet z emotikonami (no, prawie).
Wyrażenia lambda pojawiły się w C# od wersji 3.0 i od tego czasu rozprzestrzeniły się wszędzie: w LINQ, handlerach zdarzeń, w wątkach, kolekcjach i nawet w bibliotekach AI na .NET.
Wyrażenie lambda — to sposób zadeklarowania metody bezpośrednio w treści kodu, "na miejscu", bez nazwy, bardzo krótko i czytelnie. Podstawowa składnia wygląda tak:
(parametry) => wyrażenie_lub_blok_kodu
Ten "dzióbek" => nazywa się „operator lambda” albo „strzałka”.
Mała analogia
Pomyśl o tym jak o pisaniu przepisu: „Weź jabłko, pokrój je, włóż do miski”.
W C# to byłoby:
(apple) => slice(apple)
2. Przerabiamy metodę anonimową na lmbdę
Załóżmy, że mamy delegata:
delegate int SquareDelegate(int x);
Metoda anonimowa wyglądałaby tak:
SquareDelegate sq = delegate(int x) {
return x * x;
};
A teraz to samo przez wyrażenie lambda:
SquareDelegate sq = (int x) => { return x * x; };
Ale C# potrafi dopasować typy, więc możemy jeszcze skrócić:
SquareDelegate sq = x => x * x;
Imponująca kompaktowość!
Porównanie z "zwykłą" metodą i funkcją anonimową
| Sposób | Deklaracja | Gdzie można użyć | Wady |
|---|---|---|---|
| Metoda nazwana | W klasie | Wszędzie | Wymaga nazwy, więcej kodu |
| Metoda anonimowa | W kodzie | Tylko z delegatami | Bardziej rozbudowana składnia |
| Wyrażenie lambda | W kodzie | Wszędzie z delegatami | Czasem nieoczywisty typ |
3. Składnia wyrażenia lambda: warianty
Parametry
Bez parametrów:
Action hello = () => Console.WriteLine("Cześć, świat!");
Jeden parametr (można nie pisać nawiasów):
Func<int, int> inc = x => x + 1;
Kilka parametrów (trzeba nawiasy):
Func<int, int, int> sum = (a, b) => a + b;
Ciało lambdy
Jedno wyrażenie — bez klamr i return:
x => x * x
Blok kodu — klamry, potrzebny return:
(x, y) =>
{
int z = x + y;
return z * z;
}
Typy parametrów
Najczęściej nie trzeba określać typu — kompilator wywnioskuje. Ale jeśli chcesz, proszę bardzo:
(x, y) => x + y // Kompilator sam wyprowadza typy.
(int x, int y) => x + y // Można jawnie.
4. Niuanse i bardziej realne przykłady
Użycie z kolekcjami
Pamiętasz nasze ćwiczeniowe mini-aplikacje (np. lista użytkowników)? Załóżmy, że mamy tablicę liczb:
int[] numbers = { 1, 2, 3, 4, 5 };
Chcemy wybrać tylko liczby parzyste:
var evenNumbers = numbers.Where(n => n % 2 == 0);
Tu Where — metoda rozszerzająca LINQ, a warunek — nasz filtr lambda.
Przekazywanie do metody
Załóżmy, że mamy metodę-delegat:
public delegate bool Filter(int number);
public static int[] FilterNumbers(int[] data, Filter predicate)
{
var result = new List<int>();
foreach (var n in data)
if (predicate(n))
result.Add(n);
return result.ToArray();
}
Teraz przekazujemy wyrażenie lambda:
int[] evens = FilterNumbers(numbers, n => n % 2 == 0);
Lambda jako handler zdarzeń
button.Click += (sender, args) => Console.WriteLine("Przycisk naciśnięty!");
5. Wyrażenia lambda i standardowe generyczne delegaty
Lambda trudno sobie wyobrazić bez Func<>, Action<> i Predicate<>. To specjalne typy delegatów:
- Action — nic nie zwraca.
- Func — zwraca wartość.
- Predicate — zwraca bool, zwykle do filtrowania.
Przykład z Action:
Action<string> log = message => Console.WriteLine(message);
log("To wiadomość przez lambdę!");
Przykład z Func:
Func<int, int, int> multiply = (a, b) => a * b;
int product = multiply(3, 5); // 15
Przykład z Predicate:
Predicate<int> isNegative = n => n < 0;
bool test = isNegative(-7); // true
6. Lambdy z wieloma wyrażeniami: kiedy potrzebny blok
Jeśli lambda ma wykonać kilka operacji, użyj klamer i jawnego return:
Func<int, int, string> describeSum = (a, b) =>
{
int sum = a + b;
return $"Suma: {sum}";
};
Bez return kompilator się obrazi (ale nie wytłumaczy dokładnie dlaczego — po prostu napisze, że "nie wszystkie ścieżki zwracają wartość").
7. Zwracanie void: Action
Gdy lambda nic nie zwraca, użyj Action:
Action greet = () => Console.WriteLine("Uśmiechaj się — kod się kompiluje!");
Lambda może zawierać dowolną liczbę instrukcji:
Action<int> printSquare = x =>
{
int sq = x * x;
Console.WriteLine($"Kwadrat liczby {x} jest równy {sq}");
};
8. Ograniczenia i typowe błędy
Czasem chce się napisać lambdę bardzo swobodnie, ale kompilator nie zawsze się zgadza.
Nie można zadeklarować zmiennej o tej samej nazwie, co zmienna przechwycona z zewnątrz.
Trzeba pilnować typów: jeśli sygnatura delegata i lambdy się nie zgadzają — będzie błąd.
Zwrócenie wartości jest obowiązkowe, jeśli delegat wymaga wartości zwracanej.
W złożonych lambdach nie zapominaj o klamrach. Jedno wyrażenie — bez klamer i return. Kilka instrukcji — klamry i return.
GO TO FULL VERSION