C# jest jednym z najprzyjemniejszych języków, w jakich do tej pory mi się pisało kod (i do tej pory pisze) 😊. W tym artykule pokażę Ci jeden z najbardziej pożytecznych zapisów jakie warto znać w języku C#, a mianowicie wyrażenie lambda 🎉! Przedstawię nie tylko samą definicję, lecz także zasady składni jakich trzeba przestrzegać oraz praktyczny przykład zastosowania. Zapraszam 😄!
PREZENTUJĘ CZYM JEST WYRAŻENIE LAMBDA W JĘZYKU C#!
Zanim rozpoczniemy temat, uprzedzę Cię, że potrzebna Ci będzie wiedza o delegatach (funkcjach anonimowych), aby dokładnie rozumieć działanie wyrażeń lambda, więc jeżeli jeszcze nie przeczytałeś(-aś) mojego artykułu na temat delegatów, to najlepiej zacznij od niego i potem wróć tutaj 😉.
Najpierw wyjaśnienie samego terminu, abyśmy mieli jasność co do nazewnictwa 👍. Potem przejdziemy do ukazania reguł składniowych i zależności, a na sam koniec, krótki przykład użycia 🙂!
Czym jest wyrażenie lambda w języku C#?
Wyrażenie lambda w C# jest konstrukcją stanowiącą skróconą formę definicji delegata wstawianego w miejsce parametru metody przyjmującej delegat 🌟, które najczęściej dotyczą obsługi kolekcji (lecz nie muszą). W kontekście samego działania, nie różni się niczym od delegata, natomiast w zapisie jest o wiele bardziej zwięzłe i składające się z samej najważniejszej części, jaką są instrukcje 😊!
Właśnie z uwagi na czytelność wyrażenia lambda są chętnie stosowane i uznałbym je za jedną z najcenniejszych konstrukcji w języku C# jakie możesz poznać i regularnie z nich korzystać podczas programowania 🤩! Przy nabraniu pewnej wprawy, sam(a) się możesz zdziwić jak Twój kod będzie robił to samo, tylko w znacznie mniejszej liczbie linijek, przez co będzie jeszcze bardziej profesjonalny ❤️!
Jak utworzyć wyrażenie lambda w języku C#?
W C#, postać wyrażenia lambda zależy od liczby parametrów jakie ma przyjmować oraz liczby instrukcji ⚠️. Dlatego też pokażę 3 różne przykłady, abyś mógł/mogła dostrzec kluczowe fragmenty w składni 👇:
- brak parametrów, jedna instrukcja,
- jeden parametr, jedna instrukcja,
- 2 parametry, wiele instrukcji.
Zaczynamy ⏩!
Jak utworzyć wyrażenie lambda w języku C# bez parametrów z jedną instrukcją?
Oto przypadek, w którym wyrażenie lambda nie przyjmuje żadnych parametrów i składa się tylko z jednej instrukcji 👇:
() => [instrukcja do wykonania/wyrażenie do zwrócenia];Pierwszym elementem każdego wyrażenia lambda jest zestaw parametrów formalnych. Tutaj nie ma żadnych parametrów, zatem wpisujemy samą "pustą" parę nawiasów okrągłych 📝. To jest obowiązkowy zapis i pominięcie go skutkować będzie wystąpieniem błędu składniowego 🚫.
Następnie dodajemy charakterystyczny symbol w postaci połączenia znaku równości i znaku większości 👇:
=>To jest oznaczenie dla wyrażenia lambda 🔴! Niezależnie od wszystkiego, ten symbol zawsze musi występować zaraz po parametrach 💥! Inaczej będzie błąd składniowy 🚫!
Ostatnia rzecz, to instrukcja jaka ma zostać wykonana przez metodę akceptującą delegat po podstawieniu ⏏️. To może być zarówno wykonanie czegoś bez zwracania wartości, jak i z jej zwróceniem, co ma wpływ na typ delegata (opisuję to w dalszej części materiału ℹ️).
Ten sam średnik na sam koniec i to tyle. Tak wygląda budowa bezparametrowego wyrażenia lambda z jedną instrukcją ✔️!
No to teraz kolejny przykład - wyrażenie lambda z jednym parametrem i jedną instrukcją!
Jak utworzyć wyrażenie lambda w języku C# z jednym parametrem i jedną instrukcją?
Tak wygląda postać wyrażenia lambda w języku C#, które przyjmuje jeden parametr i zawiera jedną instrukcję 👇:
x => [instrukcja do wykonania/wyrażenie do zwrócenia];Na samym początku wpisujemy nazwę zmiennej, która oznaczać będzie wartość parametru aktualnego, czyli to, co zostanie wprowadzone podczas wywołania wyrażenia lambda ↪️. Wyjątkowo w tej sytuacji możemy opuścić nawiasy okrągłe, natomiast jak ktoś woli, to można też je wstawić otaczając nazwę zmiennej 👇:
(x) => [instrukcja do wykonania/wyrażenie do zwrócenia];Zaraz po parametrze umieszczamy ten sam charakterystyczny symbol (=>) i na końcu instrukcję jaka ma zostać wykonana. W zależności od tego, czy wyrażenie lambda ma coś zwracać na wyjściu, czy nie, wstawiamy odpowiedni "rodzaj" instrukcji ℹ️.
Całą konstrukcję kończymy średnikiem i mamy gotowe wyrażenie lambda z jednym parametrem wykonujące jedną instrukcję 🚀!
A teraz trzeci przypadek 3️⃣! Wyrażenie lambda akceptujące dwa parametry i zawierające więcej niż jedną instrukcję 😱!
Jak utworzyć wyrażenie lambda w języku C# z dwoma parametrami z wieloma instrukcjami?
Wyrażenie lambda, które ma akceptować 2 parametry i wykonywać więcej instrukcji niż jedna, przyjmuje taką formę 👇:
(x, y) =>
{
[instrukcja do wykonania];
[instrukcja do wykonania/wyrażenie do zwrócenia];
};Tutaj widzimy całkiem inną sytuację, więc przeanalizujmy sobie spokojnie tę postać 👀!
Najpierw para parametrów formalnych osadzonych w nawiasie okrągłym. Tutaj również nie wolno ich pominąć (jedynym wyjątkiem była sytuacja występowania tylko jednego parametru ℹ️), więc tutaj te 2 parametry koniecznie muszą być objęte nawiasem.
Wstawiamy dokładnie ten sam symbol dla wyrażeń lambda, a zaraz po nim instrukcje. Tylko uwaga ⚠️! Tym razem to już nie jest pojedyncza instrukcja, tylko blok instrukcji 😮! Stąd też stosujemy te same klamerki jak przy definicji metody 🔥! Wewnątrz nich umieszczamy instrukcje jakie mają się wykonać w ramach późniejszego wywołania wyrażenia lambda. Jeżeli ma ono zwracać jakąś wartość na wyjściu, to tu jest kolejny wyjątek w składni i wtedy należy obowiązkowo wstawić słowo kluczowe "return" ⚠️!
Warto pamiętać, że gdy w takiej sytuacji wyrażenie lambda składa się tylko z jednej instrukcji, to wówczas możemy pominąć wstawianie słowa "return" i poprzestać na samym wyrażeniu na wyjściu, zgodnym z typem zwracanej wartości (więcej o tym piszę w dalszej części), ponieważ kompilator samodzielnie wywnioskuje, że w tym miejscu chodzi nam o zwrócenie wartości danego wyrażenia 👍.
Zwróć jeszcze osobno uwagę na występowanie średników! Gdy mamy do czynienia z blokiem instrukcji, to wtedy obowiązuje nas standardowe postępowanie ze średnikami, tak jak przy pisaniu instrukcji poza tym blokiem, czyli jedna instrukcja, jeden średnik! Mało tego, średnik musi się znaleźć także po zdefiniowaniu całego bloku wyrażenia lambda, bo cała definicja to także osobna instrukcja 🫵!
To koniec przykładów jakie chciałem zaprezentować 🏁. Zauważ, że tutaj na razie pokazałem same postaci wyrażeń lambda bez określania typów danych ani sposobu ich "umieszczania" wewnątrz innych metod oczekujących delegata w miejsce parametru 🔥! Dopiero teraz poruszę te zagadnienia 🙂!
Jaki typ danych jest zgodny z wyrażeniem lambda w języku C#?
To zależy od tego, czy wyrażenie lambda ma na wyjściu zwracać jakąś wartość (tak jak metoda). Występują 2 podstawowe typy danych mogące reprezentować wyrażenia lambda 👇:
- Action (metoda niezwracająca żadnej wartości),
- Func (metoda zwracająca dowolną wartość).
Jeżeli wyrażenie lambda ma jedynie wykonać jakieś instrukcje bez zwracania żadnej wartości, to de facto będzie zachowywać się jak metoda typu "void", zatem jej typem danych stanie się "Action" ⏩. Z kolei, gdy ma zwracać jakąś wartość na wyjściu, to wtedy będzie to "Func" 🙂.
Zarówno "Action", jak i "Func", są wbudowanymi aliasami delegatów niezwracających wartości (pierwszy) i zwracających wartość (drugi) - stąd powstaje zgodność z wyrażeniami lambda ✅. To nic innego, jak skrócone zapisy wyrażeń delegatów 😊.
Możemy przejść do ukazania prostego przykładu działania wyrażenia lambda 🚀!
Przykład użycia wyrażenia lambda w języku C#
Aby zaprezentować idealne dopasowanie wyrażeń lambda do kolekcji (struktur danych zawierających wiele elementów danego typu ℹ️), dajmy na to, że dysponujemy listą kilku liczb całkowitych i chcemy każdą z nich podnieść do kwadratu, a następnie wypisać je wszystkie w konsoli 🧩.
Mając wiedzę o stosowaniu wyrażeń lambda, możemy w metodzie "main" napisać kod w taki sposób 👇:
List<int> numbers = new() {1, 2, 3, 4};
Func<int, int> GetSquareOf = n => n*n;
List<int> squaredNumbers = numbers.Select(GetSquareOf).ToList();
Action<int> WriteNumber = n => Console.WriteLine(n);
squaredNumbers.ForEach(WriteNumber);Prześledźmy każdą instrukcję 👀!
Najpierw definiujemy nową listę liczb całkowitych i umieszczamy 4 liczby 🔢. W kolejnej instrukcji mamy tworzenie delegata jako wyrażenia lambda i typu "Func", ponieważ będzie ona zwracać wynik w postaci kwadratu liczby podanej w parametrze aktualnym 🔥! "Func" przyjmuje w nawiasach trójkątnych 2 typy danych, które oznaczają typ parametru formalnego (liczba całkowita) oraz wynik zwracany na wyjściu (także liczba całkowita).
Wyrażenie lambda niezwracająca żadnej wartości na wyjściu, przyjmuje typ danych jako "Action".
Kolejną instrukcją jest zdefiniowanie nowej listy powstałej w wyniku wywołania metody "Select", która w wolnym tłumaczeniu oznacza: "dla każdego elementu, wykonaj na nim coś i zwróć nowy wynik w jego miejsce" (oczywiście zawsze możesz posiłkować się oficjalną dokumentacją Microsoftu 📖)!
Zauważ co i jak wstawiamy w miejsce parametru metody "Select" 😮! Metoda przyjmuje delegat zwracający wynik o typie zgodnym z typem elementów listy (czyli "int")! Czyli krócej, oczekuje podania metody typu "Func"! Zatem wstawiamy do środka nasze wyrażenie lambda, tylko zobacz, że nie wstawiamy żadnych nawiasów okrągłych zaraz po nazwie ✋! To nie jest wywołanie metody, tylko wskazanie metody jaka wykona się już w środku metody "Select" (to dlatego, że technicznie rzecz ujmując, przekazujemy wskaźnik do funkcji ⤵️)!
Pod sam koniec tego wywołania, dopisujemy dodatkowo wywołanie metody "ToList", aby z postaci "IEnumerable" otrzymać listę 👍!
Ostatnią zdefiniowaną zmienną jest kolejne wyrażenie lambda tym razem typu "Action", czyli skupi się jedynie na wykonaniu instrukcji bez zwracania niczego. Tutaj przyjmuje jeden parametr typu "int" i taki sam powinien się znaleźć wewnątrz nawiasów trójkątnych 🙂. Tutaj jedyną instrukcją jest wywołanie metody "Console.WriteLine", a do środka przekazujemy element w postaci liczby. To jest cała definicja tego wyrażenia 😄!
Ostatnia instrukcja w powyższym przykładzie odpowiada za wypisanie każdej z liczb podniesionej do kwadratu. Korzystamy z metody "ForEach", która akceptuje delegat tym razem niezwracający żadnej wartości (czyli metoda typu "void"), co w 100% zgadza się z definicją naszego wyrażenia lambda ❤️, także wprowadzamy go w miejsce parametru i znowu nie wstawiamy żadnych nawiasów okrągłych, gdyż to jest tylko wskazanie co ma się wykonać, a nie samo wywołanie 💡!
Typem danych dla wyrażenia lambda mającego zwrócić wartość odpowiedniego typu na wyjściu, jest "Func".
Na tym kończy się przykład. Gdy uruchomisz sobie powyższy kod wewnątrz funkcji "main", to w konsoli ujrzysz cztery liczby podniesione do kwadratu, czego udało się dokonać stosując potęgę wyrażeń lambda 🤩! Jak sam(a) widzisz, całość zadania udało się zrealizować używając zaledwie 5 jednowierszowych instrukcji 😊!
Przedstawię Ci jeszcze jeden istotny szczegół odnośnie wyrażeń lambda 🙂!
Czy wyrażenia lambda w języku C# wymagają definiowania ich na zewnątrz?
Wyrażenia lambda mogą być osadzone bezpośrednio w parametrze oczekującym delegata, jeśli zachodzi 100% zgodność z liczbą parametrów i typem zwracanej wartości ✅. Przykładowo, ten zapis 👇:
Func<int, int> GetSquareOf = n => n*n;
List<int> squaredNumbers = numbers.Select(GetSquareOf).ToList();może zostać zamieniony na jeszcze prostszy 👇:
List<int> squaredNumbers = numbers.Select(n => n*n).ToList();To oznacza, że określamy "na miejscu" co ma się zdarzyć, wyłącznie dla tego wywołania 😁! Jeżeli w danym bloku metody nie planujesz skorzystania z tego samego wyrażenia lambda dla któregokolwiek innego wywołania, ten zapis będzie jak najbardziej odpowiedni do tej sytuacji 😉!
Krótkie podsumowanie na koniec dlaczego warto znać wyrażenie lambda i kończymy 😊!
Jakie są zalety wyrażenia lambda w języku C#?
Wyrażenia lambda w języku C# zwiększają czytelność kodu zachowując jednocześnie ten sam kontekst, czyli możesz określić te same instrukcje pisząc dużo mniej słów i symboli 😎! Porównując to do omawianego wcześniej delegata, gdybyśmy chcieli wypisać w konsoli każdą liczbę w liście, to musielibyśmy go napisać w taki sposób 👇:
Action<int> WriteNumbers = delegate (int n)
{
Console.WriteLine(n);
};Z kolei wyrażenie lambda pozwala skrócić to do poniższej postaci 👇:
Action<int> WriteNumbers = n => Console.WriteLine(n);Jeżeli korzystasz z C# w wersji 7.0 lub wyższej, możesz skorzystać z lokalnej funkcji, która jak najbardziej może zostać umieszczona zamiast wyrażenia lambda i tym sposobem jeszcze bardziej uprościć sobie zapis 👇:
void WriteNumbers(int n) => Console.WriteLine(n);To już jest "lukier składniowy" odbiegający od tematu, natomiast dodałem go od siebie, bo ma związek z tematem 🍰.
Tu może tego nie widać, bo przykład jest bardzo prosty, natomiast przy bardziej złożonych blokach kodu uwierz mi, że ta różnica będzie dużo bardziej widoczna 😄!
Koniec tematu 🙂. Postarałem się wziąć pod uwagę wszystkie niuanse i różnice w składni kiedy musimy coś umieścić, a kiedy nie. Od tej pory spróbuj stopniowo przestawiać się na stosowanie wyrażeń lambda, tam gdzie tylko się da, a zobaczysz jak Twój kod robi się coraz bardziej zwięzły, a wykonuje wciąż to samo 😉!