Wyrażenia C # Lambda: Dlaczego powinienem ich używać?

309

Szybko przeczytałem dokumentację Microsoft Lambda Expression .

Ten rodzaj przykładu pomógł mi lepiej zrozumieć:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

Mimo to nie rozumiem, dlaczego to taka innowacja. To tylko metoda, która umiera, gdy kończy się „zmienna metody”, prawda? Dlaczego powinienem używać tego zamiast prawdziwej metody?

Patrick Desjardins
źródło
3
Dla tych z was, którzy odwiedzają tę stronę i nie wiedzą, co to delegatejest C #, gorąco polecam przeczytanie tego przed przeczytaniem reszty tej strony: stackoverflow.com/questions/2082615/…
Kanion Kolob

Odpowiedzi:

281

Wyrażenia lambda są prostszą składnią dla anonimowych delegatów i mogą być używane wszędzie tam, gdzie można użyć anonimowego delegata. Jednak przeciwieństwo nie jest prawdą; wyrażenia lambda można konwertować na drzewa wyrażeń, co pozwala na wiele magii, takich jak LINQ na SQL.

Poniżej znajduje się przykład wyrażenia LINQ do Objects przy użyciu anonimowych delegatów, a następnie wyrażeń lambda, aby pokazać, o ile są one łatwiejsze dla oka:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

Wyrażenia lambda i anonimowi delegaci mają przewagę nad pisaniem osobnej funkcji: implementują zamknięcia, które pozwalają przekazać stan lokalny do funkcji bez dodawania parametrów do funkcji lub tworzenia obiektów jednorazowego użytku.

Drzewa wyrażeń to nowa, bardzo potężna funkcja C # 3.0, która pozwala interfejsowi API spojrzeć na strukturę wyrażenia zamiast po prostu uzyskać odwołanie do metody, którą można wykonać. Interfejs API musi po prostu przekształcić parametr delegowany w Expression<T>parametr, a kompilator wygeneruje drzewo wyrażeń z lambda zamiast anonimowego delegata:

void Example(Predicate<int> aDelegate);

nazywany jak:

Example(x => x > 5);

staje się:

void Example(Expression<Predicate<int>> expressionTree);

Ten ostatni przejdzie reprezentację abstrakcyjnego drzewa składni, która opisuje wyrażenie x > 5. LINQ do SQL polega na tym zachowaniu, aby móc włączyć wyrażenia C # w wyrażenia SQL wymagane do filtrowania / zamawiania / itp. Po stronie serwera.

Neil Williams
źródło
1
Bez zamknięć możesz używać metod statycznych jako wywołań zwrotnych, ale nadal musisz zdefiniować te metody w niektórych klasach, niemal na pewno zwiększając zakres takiej metody poza zamierzone użycie.
DK.
10
FWIW, to może mieć zamknięć z anonimowego delegata, dzięki czemu nie trzeba ściśle lambdy za to. Jagnięta są po prostu znacznie bardziej czytelne niż anonimowi delegaci, bez których użycie Linq spowodowałoby krwawienie oczu.
Benjol
138

Anonimowe funkcje i wyrażenia są przydatne w przypadku metod jednorazowych, które nie korzystają z dodatkowej pracy wymaganej do stworzenia pełnej metody.

Rozważ ten przykład:

 string person = people.Find(person => person.Contains("Joe"));

przeciw

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

Są to funkcjonalnie równoważne.

Joseph Daigle
źródło
8
Jak zdefiniowano by metodę Find () do obsługi tego wyrażenia lambda?
Patrick Desjardins
3
Predykat <T> jest tym, czego oczekuje metoda Find.
Darren Kopp
1
Ponieważ moje wyrażenie lambda pasuje do kontraktu dla Predicate <T>, metoda Find () to akceptuje.
Joseph Daigle
czy miałeś na myśli „string person = people.Find (persons => persons.Contains („ Joe ”));”
Gern Blanston,
5
@ FKCoder, nie, nie robi tego, chociaż mogłoby być jaśniejsze, gdyby powiedział: „string person = people.Find (p => p.Contains („ Joe ”));”
Benjol
84

Uznałem je za przydatne w sytuacji, gdy chciałem zadeklarować moduł obsługi dla jakiegoś zdarzenia kontrolnego, używając innej kontrolki. Aby to zrobić normalnie, musisz przechowywać referencje kontrolek w polach klasy, abyś mógł użyć ich w innej metodzie niż zostały utworzone.

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

dzięki wyrażeniom lambda możesz używać go w następujący sposób:

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

Dużo łatwiej.

agnieszka
źródło
W pierwszym przykładzie dlaczego nie przesłać nadawcy i uzyskać wartość?
Andrew
@Andrew: W tym prostym przykładzie nie jest konieczne użycie nadawcy, ponieważ dotyczy tylko jednego komponentu, a użycie pola bezpośrednio zapisuje rzut, co poprawia przejrzystość. W rzeczywistym scenariuszu osobiście wolałbym też zamiast tego używać nadawcy. Zazwyczaj używam jednego modułu obsługi zdarzeń dla kilku zdarzeń, jeśli to możliwe, dlatego muszę zidentyfikować faktycznego nadawcę.
Chris Tophski
35

Lambda usunął na przykład anonimową składnię delegatów C # 2.0

Strings.Find(s => s == "hello");

Wykonano w C # 2.0 w następujący sposób:

Strings.Find(delegate(String s) { return s == "hello"; });

Funkcjonalnie robią dokładnie to samo, jest to po prostu bardziej zwięzła składnia.

FlySwat
źródło
3
Nie są do końca tym samym - jak wskazuje @Neil Williams, można wyodrębnić AST lambdasa za pomocą drzewek ekspresyjnych, podczas gdy anonimowych metod nie można używać w ten sam sposób.
ljs
jest to jedna z wielu innych zalet lambda. Pomaga lepiej zrozumieć kod niż anonimowe metody. z pewnością nie jest intencją tworzenia lambda, ale są to scenariusze, w których można go częściej używać.
Guruji,
29

To tylko jeden ze sposobów użycia wyrażenia lambda. Możesz użyć wyrażenia lambda w dowolnym miejscu, w którym możesz użyć delegata. To pozwala robić takie rzeczy:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

Ten kod przeszuka listę w poszukiwaniu hasła pasującego do słowa „cześć”. Innym sposobem na to jest przekazanie delegata do metody Find, jak poniżej:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

EDYTOWAĆ :

W C # 2.0 można to zrobić za pomocą anonimowej składni delegatów:

  strings.Find(delegate(String s) { return s == "hello"; });

Lambda znacznie wyczyściła tę składnię.

Scott Dorman
źródło
2
@Jonathan Holland: Dziękujemy za edycję i dodanie anonimowej składni delegatów. Ładnie uzupełnia przykład.
Scott Dorman
co to jest anonimowy delegat? // przepraszam, jestem nowy w c #
HackerMan
1
@HackerMan, pomyśl o anonimowym uczestniku jako funkcji, która nie ma „imienia”. Nadal definiujesz funkcję, która może mieć dane wejściowe i wyjściowe, ale ponieważ jest to nazwa, nie możesz bezpośrednio się do niej odwoływać. W powyższym kodzie definiujesz metodę (która pobiera stringa zwraca a bool) jako parametr Findsamej metody.
Scott Dorman
22

Microsoft dał nam czystszy, wygodniejszy sposób tworzenia anonimowych delegatów zwanych wyrażeniami Lambda. Jednak niewiele uwagi poświęca się części wyrażeń tego wyrażenia. Microsoft wydał całą przestrzeń nazw System.Linq.Expressions , która zawiera klasy do tworzenia drzew wyrażeń opartych na wyrażeniach lambda. Drzewa wyrażeń składają się z obiektów reprezentujących logikę. Na przykład x = y + z jest wyrażeniem, które może być częścią drzewa wyrażeń w .Net. Rozważ następujący (prosty) przykład:

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

Ten przykład jest trywialny. I jestem pewien, że myślisz: „Jest to bezużyteczne, ponieważ mógłbym bezpośrednio utworzyć delegata zamiast tworzyć wyrażenie i kompilować je w czasie wykonywania”. I miałbyś rację. Ale to stanowi podstawę dla drzewek ekspresyjnych. Istnieje wiele wyrażeń dostępnych w przestrzeni nazw wyrażeń i możesz zbudować własne. Myślę, że możesz zauważyć, że może to być przydatne, gdy nie wiesz dokładnie, jaki algorytm powinien być w czasie projektowania lub kompilacji. Widziałem gdzieś przykład użycia tego do napisania kalkulatora naukowego. Możesz także użyć go do systemów bayesowskich lub do programowania genetycznego(AI). Kilka razy w mojej karierze musiałem napisać funkcjonalność podobną do Excela, która pozwoliła użytkownikom na wprowadzanie prostych wyrażeń (dodawanie, dodawanie podmian itp.) W celu obsługi dostępnych danych. W wersji pre-.Net 3.5 musiałem skorzystać z jakiegoś języka skryptowego zewnętrznego dla C # lub musiałem użyć funkcji emitowania kodu w refleksji, aby stworzyć kod .Net w locie. Teraz użyłbym drzewek wyrażeń.

Jason Jackson
źródło
12

Pozwala to uniknąć konieczności definiowania metod, które są używane tylko raz w określonym miejscu, z dala od miejsca, w którym są używane. Dobrym zastosowaniem są komparatory dla ogólnych algorytmów, takich jak sortowanie, w których można następnie zdefiniować niestandardową funkcję sortowania, w której wywołuje się sortowanie, a nie zmuszać do szukania gdzie indziej sortowania.

I to nie jest tak naprawdę innowacja. LISP ma funkcje lambda od około 30 lat lub dłużej.

workmad3
źródło
6

Można również znaleźć użycie wyrażeń lambda w pisaniu ogólnych kodów, aby działać zgodnie z twoimi metodami.

Na przykład: Funkcja ogólna do obliczania czasu potrzebnego na wywołanie metody. (tj. Actiontutaj)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

I możesz wywołać powyższą metodę za pomocą wyrażenia lambda w następujący sposób:

var timeTaken = Measure(() => yourMethod(param));

Wyrażenie pozwala uzyskać wartość zwracaną z metody i również parametry

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));
Gunasekaran
źródło
5

Wyrażenie lambda to zwięzły sposób reprezentowania anonimowej metody. Zarówno metody anonimowe, jak i wyrażenia Lambda pozwalają na zdefiniowanie implementacji metody bezpośrednio, jednak metoda anonimowa wyraźnie wymaga zdefiniowania typów parametrów i typu zwracanego dla metody. Wyrażenie Lambda korzysta z funkcji wnioskowania o typie w C # 3.0, która pozwala kompilatorowi wnioskować o typie zmiennej na podstawie kontekstu. Jest to bardzo wygodne, ponieważ oszczędza nam dużo pisania!

Vijesh VP
źródło
5

Wyrażenie lambda jest jak anonimowa metoda napisana zamiast instancji delegowanej.

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

Rozważ wyrażenie lambda x => x * x;

Wartością parametru wejściowego jest x (po lewej stronie =>)

Logika funkcji to x * x (po prawej stronie =>)

Kod wyrażenia lambda może być blokiem instrukcji zamiast wyrażenia.

x => {return x * x;};

Przykład

Uwaga: Funcjest predefiniowanym ogólnym delegatem.

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

Bibliografia

  1. W jaki sposób delegata i interfejsu można używać zamiennie?
LCJ
źródło
4

Często używasz tej funkcji tylko w jednym miejscu, więc stworzenie metody po prostu zaśmieca klasę.

Darren Kopp
źródło
3

Jest to sposób na wykonanie niewielkiej operacji i umieszczenie jej bardzo blisko miejsca, w którym jest używana (podobnie jak deklarowanie zmiennej w pobliżu jej punktu użycia). Ma to na celu zwiększenie czytelności kodu. Anonimizując wyrażenie, znacznie utrudniasz komuś złamanie kodu klienta, jeśli funkcja jest używana gdzie indziej i zmodyfikowana w celu „ulepszenia”.

Podobnie, dlaczego musisz używać foreach? Możesz zrobić wszystko w foreach za pomocą zwykłej pętli for lub po prostu używając IEnumerable bezpośrednio. Odpowiedź: nie potrzebujesz go, ale dzięki temu twój kod jest bardziej czytelny.

cokół
źródło
0

Innowacja polega na bezpieczeństwie i przejrzystości. Chociaż nie deklarujesz typów wyrażeń lambda, są one wywnioskowane i mogą być używane przez wyszukiwanie kodu, analizę statyczną, narzędzia refaktoryzacji i odbicie środowiska uruchomieniowego.

Na przykład, zanim mógłeś użyć SQL i uzyskać atak wstrzykiwania SQL, ponieważ haker przekazał ciąg znaków, w którym zwykle oczekiwana była liczba. Teraz użyłbyś wyrażenia lambda LINQ, które jest przed tym chronione.

Budowanie interfejsu API LINQ na czystych delegatach nie jest możliwe, ponieważ wymaga połączenia drzew wyrażeń przed ich oceną.

W 2016 r. Większość popularnych języków obsługuje wyrażenia lambda , a C # był jednym z pionierów tej ewolucji wśród głównych języków imperatywnych.

battlmonstr
źródło
0

To chyba najlepsze wyjaśnienie, dlaczego warto używać wyrażeń lambda -> https://youtu.be/j9nj5dTo54Q

Podsumowując, ma to na celu poprawę czytelności kodu, zmniejszenie prawdopodobieństwa błędów przez ponowne użycie zamiast replikacji kodu oraz wykorzystanie optymalizacji za kulisami.

kawa
źródło
0

Największą zaletą wyrażeń lambda i funkcji anonimowych jest fakt, że pozwalają one klientowi (programistowi) biblioteki / frameworka na wstrzykiwanie funkcjonalności za pomocą kodu w danej bibliotece / frameworku (ponieważ jest to LINQ, ASP.NET Core i wiele innych) w sposób, którego zwykłe metody nie mogą. Jednak ich siła nie jest oczywista dla jednego programisty aplikacji, ale dla tego, który tworzy biblioteki, z których później będą korzystać inni, którzy będą chcieli skonfigurować zachowanie kodu biblioteki lub tego, który korzysta z bibliotek. Tak więc kontekstem skutecznego użycia wyrażenia lambda jest użycie / stworzenie biblioteki / frameworka.

Ponieważ opisują kod jednorazowego użycia, nie muszą należeć do klasy, w której doprowadzi to do większej złożoności kodu. Wyobraź sobie, że musisz deklarować klasę z niejasnym skupieniem za każdym razem, gdy chcieliśmy skonfigurować działanie obiektu klasy.

Grigoris Dimitroulakos
źródło