Lambda expressions – brzmi groźnie. To właśnie tym elementem języka C# 3.0 straszono programistów (a raczej programiści straszyli się nawzajem) jeszcze dobre kilkanaście miesięcy przed premierą .NET 3.5. A bo to “funkcyjne”, a bo to “nowe”, “nieznane” i trudne do pojęcia. Jak za chwilę zobaczymy – nic bardziej mylnego. Jeżeli kiedykolwiek korzystałeś z delegatów, to umiesz korzystać także z metod anonimowych. Jeżeli umiesz korzystać z metod anonimowych, to… to są właśnie wyrażenia lambda, tylko trochę bardziej topornie zapisane!
W tym poście nie będę zajmował się samym mechanizmem delegatów, to materiał na osobny wpis (który być może kiedyś powstanie).
Na początek zaprezentuję przykład, który pozwoli nam rozłożyć zagadnienie na czynniki pierwsze:
1: delegate TResult MyFunc<TResult, T1, T2>(T1 arg1, T2 arg2);
2: int Process(int a, int b, MyFunc<int, int, int> operation)
3: {
4: return operation(a, b);
5: }
W .NET 3.5 nie musimy tworzyć własnego delegata – możemy posłużyć się klasą Func:
int Process(int a, int b, Func<int, int, int> operation);
Zadanie metody Process jest proste: przyjąć dwie liczby oraz operację, która ma być na nich wykonana, a następnie zwrócić wynik owej operacji. Załóżmy, że zależy nam na dodaniu tych liczb. Na dzień dzisiejszy mamy aż 4 (cztery!) sposoby na uzyskanie takiego efektu. Po kolei:
W .NET 1.x mogliśmy użyć standardowej, najbardziej “rozwlekłej” notacji składającej się z dwóch kroków: deklaracji metody i przekazania jej nazwy. Dwa absolutnie równoważne sposoby to:
1: private int Adder(int arg1, int arg2)
2: {
3: return arg1 + arg2;
4: }
5:
6: int longest = Process(1, 2, new MyFunc<int, int, int>(Adder));
7: int shorter = Process(2, 3, Adder);
Twórcy .NET 2.0 uraczyli nas możliwością bezpośredniego przekazania kodu, czyli stworzenie metody anonimowej:
int anonymousMethod = Process(3, 4, delegate(int a1, int a2) { return a1 + a2; });
W C# 3.0 możemy z kolei zrobić coś takiego:
int lambdaExpression = Process(4, 5, (a1, a2) => a1 + a2);
Zaglądając Reflectorem zobaczymy:
1: [CompilerGenerated]
2: private static MyFunc<int, int, int> CS$<>9__CachedAnonymousMethodDelegate2; // metoda anonimowa
3: [CompilerGenerated]
4: private static MyFunc<int, int, int> CS$<>9__CachedAnonymousMethodDelegate3; // wyrażenie lambda
5:
6: private void Foo()
7: {
8: int longest = Process(1, 2, new MyFunc<int, int, int>(Program.Adder));
9: int shorter = Process(2, 3, new MyFunc<int, int, int>(Program.Adder)); // identyczne jak powyżej
10: if (CS$<>9__CachedAnonymousMethodDelegate2 == null)
11: {
12: CS$<>9__CachedAnonymousMethodDelegate2 = delegate (int a1, int a2) {
13: return a1 + a2;
14: };
15: }
16: int anonymousMethod = Process(3, 4, CS$<>9__CachedAnonymousMethodDelegate2);
17: if (CS$<>9__CachedAnonymousMethodDelegate3 == null)
18: {
19: CS$<>9__CachedAnonymousMethodDelegate3 = delegate (int a1, int a2) {
20: return a1 + a2;
21: };
22: }
23: int lambdaExpression = Process(4, 5, CS$<>9__CachedAnonymousMethodDelegate3);
24: }
Wniosek jest oczywisty: wyrażenia lambda to po prostu nowy sposób utworzenia anonimowej metody – i NIC poza tym. Kod wygenerowany dla lambda niczym się nie różni od kodu wygenerowanego dla anonymous method. O co więc tyle szumu? Tylko i wyłącznie o zapis, który jest teraz bardziej “funkcyjny” (lub “elegancki”, a już na pewno – krótszy). Nie podajemy typów parametrów, nie używamy “return”… wszystko dzieje się “samo”.
Przeczytany, zrozumiany, dzięki :)
p.s. dodaj "kick it" albo coś w tym rodzaju, to będziemy klikać i wykopywać :]
Spx, more is coming.
Co do kicków, diggów itd to bez szczególnej przyczyny wyłączyłem je. Nie wiem właściwie dlaczego więc może faktycznie nadejdzie moment gdy "enabled" zostanie na nich wciśnięte, dzięki za pomysł:).
Krotko i na temat. 5 punktow! :)
Próbuję poznać i zrozumieć LINQ-SQL od kilku dni. Zanim przeczytałem Twój artykuł, patrzyłem na => => => jak na pismo supełkowe. Teraz zatrybiłem. Dzięki.