Lambda-Ausdrücke (C#-Programmierhandbuch)

Ein Lambda-Ausdruck ist eine anonyme Funktion, die Sie verwenden können, um Delegaten oder Ausdrucksbaumstrukturtypen zu erstellen.Indem Sie Lambda-Ausdrücke verwenden, können Sie lokale Funktionen schreiben, die als Argumente übergeben oder als Wert von Funktionsaufrufen zurückgegeben werden können.Lambda-Ausdrücke sind für das Schreiben von LINQ-Abfrageausdrücken besonders hilfreich.

Um einen Lambda-Ausdruck zu erstellen, geben Sie Eingabeparameter (falls vorhanden) auf der linken Seite des Lambda-Operators => an, und setzen Sie den Ausdruck oder der Anweisungsblock auf die andere Seite.Beispielsweise gibt der Lambda-Ausdruck x => x * x einen Parameter, der x Namen auf und gibt den Wert von x quadrierte zurück.Sie können diesen Ausdruck einen Delegattyp zuweisen, wie im folgenden Beispiel gezeigt:

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

So erstellen Sie einen Typ für die Ausdrucksbaumstruktur:

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

Der Operator => verfügt über die gleiche Rangfolge wie die Zuweisung (=) und ist rechtsassoziativ.

Lambda-Ausdrücke werden in methodenbasierten LINQ-Abfragen als Argumente für Standardabfrageoperator-Methoden wie Where verwendet.

Wenn Sie zum Aufrufen der Where-Methode in der Enumerable-Klasse methodenbasierte Syntax verwenden (wie in LINQ to Objects und LINQ to XML), ist der Parameter ein Delegattyp System.Func<T, TResult>.Ein Lambda-Ausdruck ist die einfachste Möglichkeit zum Erstellen dieses Delegaten.Wenn Sie dieselbe Methode z. B. in der System.Linq.Queryable-Klasse aufrufen (wie in LINQ to SQL), handelt es sich bei dem Parametertyp um eine System.Linq.Expressions.Expression<Funktion>, wobei Funktion für beliebige Funktionsdelegaten mit bis zu 16 Eingabeparametern steht.Wie gesagt, ein Lambda-Ausdruck ist eine sehr präzise Methode, diese Ausdrucksbaumstruktur zu erstellen.Durch die Lambda-Ausdrücke können die Where-Aufrufe ähnlich aussehen, obwohl sich der mithilfe des Lambda-Ausdrucks erstellte Objekttyp unterscheidet.

Beachten Sie im vorigen Beispiel, dass die Signatur des Delegaten über einen implizit typisierten Eingabeparameter vom Typ int verfügt und int zurückgibt.Der Lambda-Ausdruck kann in einen Delegaten dieses Typs konvertiert werden, da er auch über einen Eingabeparameter (x) und einen Rückgabewert verfügt, der vom Compiler implizit in den Typ int konvertiert werden kann.(Der Typrückschluss wird in den folgenden Abschnitten ausführlicher erläutert.) Wenn der Delegat durch Verwendung des Eingabeparameters 5 aufgerufen wird, wird als Ergebnis 25 zurückgegeben.

Lambda-Ausdrücke sind auf der linken Seite des Operators is oder as nicht zugelassen.

Alle Einschränkungen für anonyme Methoden gelten auch für Lambda-Ausdrücke.Weitere Informationen finden Sie unter Anonyme Methoden (C#-Programmierhandbuch).

Ausdruckslambdas

Ein Lambda-Ausdruck mit einem Ausdruck auf der rechten Seite wird als Ausdruckslambda bezeichnet.Ausdruckslambdas werden häufig bei der Erstellung von Ausdrucksbaumstrukturen (C# und Visual Basic) verwendet.Ein Ausdruckslambda gibt das Ergebnis des Ausdrucks zurück und hat folgende grundlegende Form:

(input parameters) => expression

Die Klammern sind nur optional, wenn der Lambda-Ausdruck über einen Eingabeparameter verfügt; andernfalls sind sie erforderlich.Zwei oder mehr Eingabeparameter sind durch Kommas getrennt und in Klammern eingeschlossen:

(x, y) => x == y

Für den Compiler kann es schwierig oder unmöglich sein, die Eingabetypen abzuleiten.Wenn dieses Problem auftritt, können Sie die Typen explizit angeben, wie im folgenden Beispiel gezeigt:

(int x, string s) => s.Length > x

Geben Sie Eingabeparameter von 0 (null) mit leeren Klammern an:

() => SomeMethod()

Beachten Sie im vorherigen Beispiel, dass der Text eines Ausdruckslambdas ein Methodenaufruf sein kann.Wenn Sie jedoch Ausdrucksbaumstrukturen erstellen, die in einer anderen Domäne genutzt werden, z. B. SQL Server, sollten Sie in Lambda-Ausdrücken keine Methodenaufrufe verwenden.Die Methoden haben außerhalb des Kontexts der .NET Common Language Runtime keine Bedeutung.

Anweisungslambdas

Ein Anweisungslambda ähnelt einem Ausdruckslambda, allerdings sind die Anweisungen in Klammern eingeschlossen:

(input parameters) => {statement;}

Der Text eines Anweisungslambdas kann aus einer beliebigen Anzahl von Anweisungen bestehen, wobei es sich meistens um höchstens zwei oder drei Anweisungen handelt.

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

Anweisungslambdas, wie anonyme Methoden, können nicht zum Erstellen von Ausdrucksbaumstrukturen verwendet werden.

Async Lambdas

Sie können Lambda-Ausdrücke und Anweisungen leicht erstellen, die asynchrone Verarbeitung enthalten, indem async und die await Schlüsselwörter verwenden.Beispielsweise enthält das folgende Windows Forms einen Ereignishandler, der eine asynchrone Methode aufruft und erwartet, ExampleMethodAsync.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // ExampleMethodAsync returns a Task.
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Sie können den gleichen Ereignishandler hinzufügen, indem Sie ein async Lambda verwenden.Um diesen Handler hinzuzufügen, fügen Sie einen async-Modifizierer vor der Lambda-Parameterliste, wie im folgenden Beispiel gezeigt hinzu.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            // ExampleMethodAsync returns a Task.
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
        };
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Weitere Informationen dazu, wie asynchrone Methoden, finden Sie unter Asynchrone Programmierung mit Async und Await (C# und Visual Basic) erstellt und verwendet.

Lambdas mit Standardabfrageoperatoren

Viele Standardabfrageoperatoren weisen einen Eingabeparameter auf, deren Typ einem der Typen aus der Func<T, TResult>-Familie generischer Delegaten entspricht.Die Func<T, TResult>-Delegaten verwenden Typparameter zur Definition der Anzahl und des Typs der Eingabeparameter sowie des Rückgabetyps des Delegaten.Func-Delegaten sind sehr nützlich für das Kapseln von benutzerdefinierten Ausdrücken, die für jedes Element in einem Satz von Quelldaten übernommen werden.Betrachten Sie z. B. den folgenden Delegattyp:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

Der Delegat kann als Func<int,bool> myFunc instanziiert werden, wobei int ein Eingabeparameter und bool der Rückgabewert ist.Der Rückgabewert wird immer im letzten Typparameter angegeben.Func<int, string, bool> definiert einen Delegaten mit zwei Eingabeparametern, int und string und einen Rückgabetyp von bool.Der folgende Func-Delegat gibt bei einem Aufruf true oder false zurück, um anzugeben, ob der Eingabeparameter gleich 5 ist:

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

Sie können einen Lambda-Ausdruck auch dann angeben, wenn der Argumenttyp Expression<Func> ist, beispielsweise in den Standardabfrageoperatoren, die in System.Linq.Queryable definiert sind.Wenn Sie ein Expression<Func>-Argument angeben, wird der Lambda-Ausdruck in eine Ausdrucksbaumstruktur kompiliert.

Hier wird ein Standardabfrageoperator, die Count-Methode, gezeigt:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

Der Compiler kann den Typ des Eingabeparameters ableiten, Sie können ihn aber auch explizit angeben.Dieser bestimmte Lambda-Ausdruck zählt die ganzen Zahlen (n), bei denen nach dem Dividieren durch zwei als Rest 1 bleibt.

Die folgende Methode erzeugt eine Sequenz, die alle Elemente im numbers Array enthält, die auf die linke Seite der 9 sind, da dies die erste Zahl in der Sequenz ist, die die Bedingung nicht erfüllt:

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

In diesem Beispiel wird gezeigt, wie Sie mehrere Eingabeparameter angeben, indem Sie sie in Klammern einschließen.Mit der Methode werden alle Elemente im Zahlenarray zurückgegeben, bis eine Zahl erreicht wird, deren Wert kleiner ist als seine Position.Verwechseln Sie den Operator Lambda (=>) nicht mit dem Operator "Größer als oder gleich" (>=).

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

Typrückschluss in Lambda-Ausdrücken

Beim Schreiben von Lambda-Ausdrücken müssen Sie oftmals keinen Typ für die Eingabeparameter angeben, da der Compiler den Typ auf Grundlage des Lambda-Texts, des zugrunde liegenden Delegattyps und anderer Faktoren per Rückschluss ableiten kann, wie in der C#-Programmiersprachenspezifikation beschrieben.Bei den meisten Standardabfrageoperatoren entspricht die erste Eingabe dem Typ der Elemente in der Quellsequenz.Beim Abfragen von IEnumerable<Customer> wird die Eingabevariable als Customer-Objekt abgeleitet, sodass Sie auf die zugehörigen Methoden und Eigenschaften zugreifen können:

customers.Where(c => c.City == "London");

Die allgemeinen Regeln für Lambda-Ausdrücke lauten wie folgt:

  • Der Lambda-Ausdruck muss dieselbe Anzahl von Parametern enthalten wie der Delegattyp.

  • Jeder Eingabeparameter im Lambda-Ausdruck muss implizit in den entsprechenden Delegatparameter konvertiert werden können.

  • Der Rückgabewert des Lambda-Ausdrucks (falls vorhanden) muss implizit in den Rückgabetyp des Delegaten konvertiert werden können.

Beachten Sie, dass Lambda-Ausdrücke keinen eigenen Typ haben, da das allgemeine Typsystem kein internes Konzept von "Lambda-Ausdrücken" aufweist. Es kann manchmal praktisch sein, informell vom "Typ" eines Lambda-Ausdrucks zu sprechen.In einem solchen Fall bezeichnet Typ den Delegattyp bzw. den Expression-Typ, in den der Lambda-Ausdruck konvertiert wird.

Variablenbereich in Lambda-Ausdrücken

Lambda-Ausdrücke können sich auf äußere Variablen beziehen, die im Bereich der einschließenden Methode oder des Typs liegen, in dem der Lambda-Ausdruck definiert wird.Variablen, die auf diese Weise erfasst werden, werden zur Verwendung in Lambda-Ausdrücken gespeichert, auch wenn sie andernfalls außerhalb des Gültigkeitsbereichs liegen und an die Garbage Collection übergeben würden.Eine äußere Variable muss definitiv zugewiesen sein, bevor sie in einem Lambda-Ausdruck verwendet werden kann.Das folgende Beispiel veranschaulicht diese Regeln:

delegate bool D();
delegate bool D2(int i);

class Test
{
    D del;
    D2 del2;
    public void TestMethod(int input)
    {
        int j = 0;
        // Initialize the delegates with lambda expressions.
        // Note access to 2 outer variables.
        // del will be invoked within this method.
        del = () => { j = 10;  return j > input; };

        // del2 will be invoked after TestMethod goes out of scope.
        del2 = (x) => {return x == j; };
      
        // Demonstrate value of j:
        // Output: j = 0 
        // The delegate has not been invoked yet.
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.
        bool boolResult = del();

        // Output: j = 10 b = True
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);
    }

    static void Main()
    {
        Test test = new Test();
        test.TestMethod(5);

        // Prove that del2 still has a copy of
        // local variable j from TestMethod.
        bool result = test.del2(10);

        // Output: True
        Console.WriteLine(result);
           
        Console.ReadKey();
    }
}

Die folgenden Regeln gelten für den Variablenbereich in Lambda-Ausdrücken:

  • Eine erfasste Variable wird erst dann an die Garbage Collection übergeben, wenn der darauf verweisende Delegat den Gültigkeitsbereich verlässt.

  • Variablen, die in einem Lambda-Ausdruck eingeführt wurden, sind in der äußeren Methode nicht sichtbar.

  • Ein Lambda-Ausdruck kann einen ref- oder out-Parameter nicht direkt von einer einschließenden Methode erfassen.

  • Eine return-Anweisung in einem Lambda-Ausdruck bewirkt keine Rückgabe durch die einschließende Methode.

  • Ein Lambda-Ausdruck darf keine goto-, break-Anweisung oder continue-Anweisung enthalten, deren Ziel außerhalb des Texts oder im Text einer enthaltenen anonymen Funktion liegt.

C#-Programmiersprachenspezifikation

Weitere Informationen finden Sie in der C#-Sprachspezifikation. Die Sprachspezifikation ist die verbindliche Quelle für die Syntax und Verwendung von C#.

Enthaltenes Buchkapitel

Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

Siehe auch

Referenz

Anonyme Methoden (C#-Programmierhandbuch)

is (C#-Referenz)

Konzepte

C#-Programmierhandbuch

Ausdrucksbaumstrukturen (C# und Visual Basic)

Weitere Ressourcen

LINQ (Language-Integrated Query, sprachintegrierte Abfrage)

Rekursive Lambda-Ausdrücke