C # Lambda-Ausdrücke: Warum sollte ich sie verwenden?

309

Ich habe die Microsoft Lambda Expression- Dokumentation schnell gelesen .

Diese Art von Beispiel hat mir jedoch geholfen, besser zu verstehen:

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

Trotzdem verstehe ich nicht, warum es so eine Innovation ist. Es ist nur eine Methode, die stirbt, wenn die "Methodenvariable" endet, oder? Warum sollte ich dies anstelle einer echten Methode verwenden?

Patrick Desjardins
quelle
3
Für diejenigen unter Ihnen, die auf diese Seite kommen und nicht wissen, was a delegatein C # ist, empfehle ich dringend, dies zu lesen, bevor Sie den Rest dieser Seite lesen
Kolob Canyon

Antworten:

281

Lambda-Ausdrücke sind eine einfachere Syntax für anonyme Delegaten und können überall dort verwendet werden, wo ein anonymer Delegat verwendet werden kann. Das Gegenteil ist jedoch nicht der Fall; Lambda-Ausdrücke können in Ausdrucksbäume konvertiert werden, was viel Magie wie LINQ to SQL ermöglicht.

Das Folgende ist ein Beispiel für einen LINQ to Objects- Ausdruck, bei dem anonyme Delegaten und dann Lambda-Ausdrücke verwendet werden, um zu zeigen, wie viel einfacher für das Auge sie sind:

// 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();

Lambda-Ausdrücke und anonyme Delegaten haben gegenüber dem Schreiben einer separaten Funktion den Vorteil, dass sie Schließungen implementieren, mit denen Sie den lokalen Status an die Funktion übergeben können, ohne der Funktion Parameter hinzuzufügen oder Objekte zur einmaligen Verwendung zu erstellen.

Ausdrucksbäume sind eine sehr leistungsstarke neue Funktion von C # 3.0, mit der eine API die Struktur eines Ausdrucks anzeigen kann, anstatt nur einen Verweis auf eine Methode zu erhalten, die ausgeführt werden kann. Eine API muss lediglich einen Delegatenparameter in einen Expression<T>Parameter umwandeln, und der Compiler generiert einen Ausdrucksbaum aus einem Lambda anstelle eines anonymen Delegaten:

void Example(Predicate<int> aDelegate);

genannt wie:

Example(x => x > 5);

wird:

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

Letzterem wird eine Darstellung des abstrakten Syntaxbaums übergeben , der den Ausdruck beschreibt x > 5. LINQ to SQL basiert auf diesem Verhalten, um C # -Ausdrücke in die SQL-Ausdrücke umwandeln zu können, die zum Filtern / Ordnen / usw. auf der Serverseite gewünscht werden.

Neil Williams
quelle
1
Ohne Closures können Sie statische Methoden als Rückrufe verwenden, aber Sie müssen diese Methoden immer noch in einer Klasse definieren, wodurch der Umfang dieser Methode fast sicher über die beabsichtigte Verwendung hinaus erweitert wird.
DK.
10
FWIW, Sie können Schließungen mit einem anonymen Delegierten haben, daher benötigen Sie dafür keine Lambdas. Lambdas sind einfach besser lesbar als anonyme Delegierte, ohne die die Verwendung von Linq Ihre Augen bluten lassen würde.
Benjol
138

Anonyme Funktionen und Ausdrücke sind nützlich für einmalige Methoden, die nicht von der zusätzlichen Arbeit profitieren, die zum Erstellen einer vollständigen Methode erforderlich ist.

Betrachten Sie dieses Beispiel:

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

gegen

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

Diese sind funktional gleichwertig.

Joseph Daigle
quelle
8
Wie wäre die Find () -Methode definiert worden, um mit diesem Lambda-Ausdruck umzugehen?
Patrick Desjardins
3
Prädikat <T> ist das, was die Find-Methode erwartet.
Darren Kopp
1
Da mein Lambda-Ausdruck mit dem Vertrag für Prädikat <T> übereinstimmt, akzeptiert ihn die Find () -Methode.
Joseph Daigle
Meinten Sie "Zeichenfolge person = people.Find (Personen => Personen.Contains (" Joe "));"
Gern Blanston
5
@FKCoder, nein, tut er nicht, obwohl es klarer gewesen wäre, wenn er gesagt hätte "string person = people.Find (p => p.Contains (" Joe "));"
Benjol
84

Ich fand sie nützlich in einer Situation, in der ich einen Handler für das Ereignis eines Steuerelements mit einem anderen Steuerelement deklarieren wollte. Um dies normal zu tun, müssten Sie die Referenzen der Steuerelemente in Feldern der Klasse speichern, damit Sie sie in einer anderen Methode als der erstellten verwenden können.

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;
}

Dank Lambda-Ausdrücken können Sie es folgendermaßen verwenden:

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

Viel einfacher.

Agnieszka
quelle
Warum nicht im ersten Beispiel den Absender umwandeln und den Wert erhalten?
Andrew
@ Andrew: In diesem einfachen Beispiel ist es nicht erforderlich, den Absender zu verwenden, da nur eine Komponente in Frage kommt und die direkte Verwendung des Felds eine Besetzung speichert, was die Klarheit verbessert. In einem realen Szenario würde ich persönlich auch lieber den Absender verwenden. Normalerweise verwende ich einen Ereignishandler für mehrere Ereignisse, wenn möglich, und muss daher den tatsächlichen Absender identifizieren.
Chris Tophski
35

Lambda hat die anonyme Delegatensyntax von C # 2.0 bereinigt ... zum Beispiel

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

Wurde in C # 2.0 folgendermaßen erstellt:

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

Funktionell machen sie genau das Gleiche, es ist nur eine viel präzisere Syntax.

FlySwat
quelle
3
Sie sind nicht ganz dasselbe - wie @Neil Williams betont, können Sie den AST eines Lambdas mithilfe von Ausdrucksbäumen extrahieren, während anonyme Methoden nicht auf die gleiche Weise verwendet werden können.
ljs
Dies ist einer der vielen anderen Vorteile von Lambda. Es hilft, den Code besser zu verstehen als anonyme Methoden. Es ist sicherlich nicht die Absicht, Lambdas zu kreieren, aber dies sind Szenarien, in denen es häufiger verwendet werden kann.
Guruji
29

Dies ist nur eine Möglichkeit, einen Lambda-Ausdruck zu verwenden. Sie können einen Lambda-Ausdruck überall dort verwenden, wo Sie einen Delegaten verwenden können. Auf diese Weise können Sie Folgendes tun:

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");

Dieser Code durchsucht die Liste nach einem Eintrag, der dem Wort "Hallo" entspricht. Die andere Möglichkeit besteht darin, einen Delegaten wie folgt an die Find-Methode zu übergeben:

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);

EDIT :

In C # 2.0 kann dies mithilfe der anonymen Delegatensyntax erfolgen:

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

Lambda hat diese Syntax erheblich aufgeräumt.

Scott Dorman
quelle
2
@ Jonathan Holland: Vielen Dank für die Bearbeitung und das Hinzufügen der anonymen Delegatensyntax. Es vervollständigt das Beispiel schön.
Scott Dorman
Was ist ein anonymer Delegierter? // Entschuldigung, ich bin neu bei c #
HackerMan
1
@ HackerMan, stellen Sie sich einen anonymen Delegaten als eine Funktion vor, die keinen "Namen" hat. Sie definieren immer noch eine Funktion, die Eingabe und Ausgabe haben kann, aber da es sich um einen Namen handelt, können Sie nicht direkt darauf verweisen. In dem oben gezeigten Code definieren Sie eine Methode (die a nimmt stringund a zurückgibt bool) als Parameter für die FindMethode selbst.
Scott Dorman
22

Microsoft hat uns eine sauberere und bequemere Möglichkeit gegeben, anonyme Delegaten zu erstellen, die als Lambda-Ausdrücke bezeichnet werden. Dem Ausdrucksteil dieser Aussage wird jedoch nicht viel Aufmerksamkeit geschenkt . Microsoft hat einen vollständigen Namespace, System.Linq.Expressions , veröffentlicht , der Klassen zum Erstellen von Ausdrucksbäumen basierend auf Lambda-Ausdrücken enthält. Ausdrucksbäume bestehen aus Objekten, die Logik darstellen. Beispielsweise ist x = y + z ein Ausdruck, der Teil eines Ausdrucksbaums in .Net sein kann. Betrachten Sie das folgende (einfache) Beispiel:

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();
        }
    }
}

Dieses Beispiel ist trivial. Und ich bin sicher, Sie denken: "Dies ist nutzlos, da ich den Delegaten direkt hätte erstellen können, anstatt einen Ausdruck zu erstellen und ihn zur Laufzeit zu kompilieren." Und du hättest recht. Dies bildet jedoch die Grundlage für Ausdrucksbäume. In den Expressions-Namespaces stehen eine Reihe von Ausdrücken zur Verfügung, und Sie können Ihre eigenen erstellen. Ich denke, Sie können sehen, dass dies nützlich sein kann, wenn Sie nicht genau wissen, wie der Algorithmus zum Zeitpunkt des Entwurfs oder der Kompilierung aussehen soll. Ich habe irgendwo ein Beispiel dafür gesehen, wie man damit einen wissenschaftlichen Taschenrechner schreibt. Sie können es auch für Bayes'sche Systeme oder für die genetische Programmierung verwenden(AI). Einige Male in meiner Karriere musste ich Excel-ähnliche Funktionen schreiben, mit denen Benutzer einfache Ausdrücke (Additionen, Subtrationen usw.) eingeben konnten, um mit verfügbaren Daten zu arbeiten. In Pre-.Net 3.5 musste ich auf eine Skriptsprache außerhalb von C # zurückgreifen oder die Code-emittierende Funktion in Reflection verwenden, um .Net-Code im laufenden Betrieb zu erstellen. Jetzt würde ich Ausdrucksbäume verwenden.

Jason Jackson
quelle
12

Es erspart es, dass Methoden, die nur einmal an einem bestimmten Ort verwendet werden, weit entfernt von dem Ort definiert werden, an dem sie verwendet werden. Gute Verwendungszwecke sind Komparatoren für generische Algorithmen wie das Sortieren, bei denen Sie dann eine benutzerdefinierte Sortierfunktion definieren können, bei der Sie die Sortierung aufrufen, anstatt sich weiter entfernt zu zwingen, woanders zu suchen, um zu sehen, wonach Sie sortieren.

Und es ist nicht wirklich eine Innovation. LISP hat seit ungefähr 30 Jahren oder länger Lambda-Funktionen.

workmad3
quelle
6

Sie können auch die Verwendung von Lambda-Ausdrücken beim Schreiben generischer Codes finden, um auf Ihre Methoden zu reagieren.

Beispiel: Generische Funktion zur Berechnung der Zeit, die ein Methodenaufruf benötigt. (dh Actionhier drin)

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

Und Sie können die obige Methode mit dem Lambda-Ausdruck wie folgt aufrufen:

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

Mit Expression können Sie den Rückgabewert von Ihrer Methode und auch von out param abrufen

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));
Gunasekaran
quelle
5

Der Lambda-Ausdruck ist eine prägnante Möglichkeit, eine anonyme Methode darzustellen. Sowohl anonyme Methoden als auch Lambda-Ausdrücke ermöglichen es Ihnen, die Methodenimplementierung inline zu definieren. Bei einer anonymen Methode müssen Sie jedoch ausdrücklich die Parametertypen und den Rückgabetyp für eine Methode definieren. Der Lambda-Ausdruck verwendet die Typinferenzfunktion von C # 3.0, mit der der Compiler den Typ der Variablen basierend auf dem Kontext ableiten kann. Es ist sehr praktisch, weil wir dadurch viel tippen müssen!

Vijesh VP
quelle
5

Ein Lambda-Ausdruck ist wie eine anonyme Methode, die anstelle einer Delegateninstanz geschrieben wurde.

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

Betrachten Sie den Lambda-Ausdruck x => x * x;

Der Eingabeparameterwert ist x (auf der linken Seite von =>)

Die Funktionslogik ist x * x (auf der rechten Seite von =>)

Der Code eines Lambda-Ausdrucks kann ein Anweisungsblock anstelle eines Ausdrucks sein.

x => {return x * x;};

Beispiel

Hinweis: Funcist ein vordefinierter generischer Delegat.

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

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

Verweise

  1. Wie kann ein Delegat und eine Schnittstelle austauschbar verwendet werden?
LCJ
quelle
4

In vielen Fällen verwenden Sie die Funktionalität nur an einem Ort, sodass eine Methode die Klasse nur überfüllt.

Darren Kopp
quelle
3

Auf diese Weise können Sie kleine Operationen ausführen und sie sehr nahe an den Verwendungsort bringen (ähnlich wie beim Deklarieren einer Variablen in der Nähe ihres Verwendungspunkts). Dies soll Ihren Code lesbarer machen. Indem Sie den Ausdruck anonymisieren, erschweren Sie es jemandem erheblich, Ihren Client-Code zu beschädigen, wenn die Funktion an einer anderen Stelle verwendet und geändert wird, um ihn zu "verbessern".

In ähnlicher Weise, warum müssen Sie foreach verwenden? Sie können alles in foreach mit einer Plain-for-Schleife oder einfach mit IEnumerable direkt ausführen. Antwort: Sie brauchen es nicht , aber es macht Ihren Code besser lesbar.

Sockel
quelle
0

Die Innovation liegt in der Typensicherheit und Transparenz. Obwohl Sie keine Arten von Lambda-Ausdrücken deklarieren, werden sie abgeleitet und können von der Codesuche, statischen Analyse, Refactoring-Tools und Laufzeitreflexion verwendet werden.

Zum Beispiel, bevor Sie möglicherweise SQL verwendet haben und einen SQL-Injection-Angriff erhalten haben, weil ein Hacker eine Zeichenfolge übergeben hat, an der normalerweise eine Zahl erwartet wurde. Jetzt würden Sie einen LINQ-Lambda-Ausdruck verwenden, der davor geschützt ist.

Das Erstellen einer LINQ-API auf reinen Delegaten ist nicht möglich, da vor der Auswertung Ausdrucksbäume miteinander kombiniert werden müssen.

Im Jahr 2016 haben die meisten populären Sprachen Unterstützung für Lambda-Ausdrücke , und C # war einer der Pioniere in dieser Entwicklung unter den wichtigsten imperativen Sprachen.

Battlmonstr
quelle
0

Dies ist vielleicht die beste Erklärung, warum Lambda-Ausdrücke verwendet werden sollten -> https://youtu.be/j9nj5dTo54Q

Zusammenfassend lässt sich sagen, dass die Lesbarkeit des Codes verbessert, das Fehlerrisiko durch Wiederverwendung und nicht durch Replikation von Code verringert und die Optimierung hinter den Kulissen wirksam eingesetzt werden soll.

Kaffee
quelle
0

Der größte Vorteil von Lambda-Ausdrücken und anonymen Funktionen besteht darin, dass der Client (Programmierer) einer Bibliothek / eines Frameworks Funktionen mithilfe von Code in die angegebene Bibliothek / das angegebene Framework einfügen kann (da es sich um LINQ, ASP.NET Core und handelt) viele andere) auf eine Weise, die die regulären Methoden nicht können. Ihre Stärke ist jedoch nicht für einen einzelnen Anwendungsprogrammierer offensichtlich, sondern für denjenigen, der Bibliotheken erstellt, die später von anderen verwendet werden, die das Verhalten des Bibliothekscodes konfigurieren möchten, oder für diejenigen, die Bibliotheken verwenden. Der Kontext für die effektive Verwendung eines Lambda-Ausdrucks ist also die Verwendung / Erstellung einer Bibliothek / eines Frameworks.

Da sie den Code für die einmalige Verwendung beschreiben, müssen sie nicht Mitglied einer Klasse sein, in der dies zu einer höheren Codekomplexität führt. Stellen Sie sich vor, Sie müssen jedes Mal eine Klasse mit unklarem Fokus deklarieren, wenn wir den Betrieb eines Klassenobjekts konfigurieren möchten.

Grigoris Dimitroulakos
quelle