Lambda-Ausdrücke (C#-Programmierhandbuch)
Bei einem Lambda-Ausdruck handelt es sich um eine anonyme Funktion, die Ausdrücke und Anweisungen enthalten und für die Erstellung von Delegaten oder Ausdrucksbaumstrukturen verwendet werden kann.
Alle Lambda-Ausdrücke verwenden den Operator Lambda =>, der so viel bedeutet wie "wechselt zu". Auf der linken Seite des Operators Lambda werden die Eingabeparameter angegeben (falls vorhanden), und auf der rechten Seite befindet sich der Ausdruck oder Anweisungsblock. Der Lambda-Ausdruck x => x * x bedeutet "x wechselt x Mal zu x". Dieser Ausdruck kann wie folgt einem Delegattyp zugewiesen werden:
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.
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.
Mit der folgenden Methode wird eine Sequenz erzeugt, die alle Elemente im numbers-Array enthält, die vor der 9 auftreten, 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#.
Enthaltene 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)
Konzepte
Ausdrucksbaumstrukturen (C# und Visual Basic)