1. Einführung
In echten Projekten verbringt fast jeder zweite Programmierer einen guten Teil seiner Zeit mit Collections: filtern, zählen, suchen, neu sortieren, hineintun, herausnehmen — kurz gesagt, man behandelt sie fast wie den Kühlschrank vor dem Schlafengehen. Besonders häufig muss man Daten extrahieren, verarbeiten und aggregieren — sei es eine Liste von Benutzern, Produkte im Katalog, Textzeilen oder beliebige andere Arrays.
Fast alle modernen Collections in .NET unterstützen funktionale Methoden — wie Where, Select, Find, Any, All und weitere. Ihre Stärke liegt in der Universalität und dem knappen Stil: du übergibst einfach ein "Stück Logik" als Lambda-Ausdruck, und die Collection wird lebendig, als hättest du einen neuen Motor gestartet.
LINQ (Language Integrated Query) ist nicht nur syntaktischer Zucker, sondern eine kleine Sprache innerhalb von C#, die es erlaubt, Anfragen an Daten so zu schreiben, als würdest du SQL oder Excel benutzen. Nur besser: direkt im Code, mit Autovervollständigung, Typen und Debugger.
Aber all diese Magie funktioniert dank Delegates — und jedes Mal eine eigene Methode für eine Filterung zu schreiben ist ziemlich lästig. Hier kommen Lambda-Ausdrücke ins Spiel als kleine Inline-Funktionen, die schweren Code in eleganten und aussagekräftigen Code verwandeln.
2. Lambda-Ausdrücke in Standard-Collection-Methoden
Lambda-Ausdrücke zeigen ihre Stärken besonders in Standard-Collection-Methoden, die auf Delegates basieren, wie Find, Exists, ForEach und vielen anderen.
Beispiel: Suche nach Bedingung
Angenommen, du hast eine Liste von Produkten:
using System;
using System.Collections.Generic;
// Unser Produktklasse
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
var products = new List<Product>
{
new Product { Name = "Coffee", Price = 100 },
new Product { Name = "Tea", Price = 70 },
new Product { Name = "Milk", Price = 80 }
};
// Finden wir das erste teure Produkt (>90)
Product expensive = products.Find(p => p.Price > 90); // Verwenden wir Lambda!
Console.WriteLine(expensive?.Name); // => Coffee
Ohne Lambda müsstest du eine separate Methode oder eine alte anonyme Funktion schreiben. So ist es eine Zeile und der Code liest sich wie Englisch: "Find das Produkt, wo der Preis größer als 90 ist".
Beispiel: Überprüfung auf vorhandenes Produkt
bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True (weil "Tea" günstiger als 75 ist)
Beispiel: Verarbeitung aller Elemente (ForEach)
Manchmal muss man etwas mit jedem Element machen:
products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} euro"));
Analogie
Kurz gesagt: Lambda-Ausdrücke in Collections sind wie der "Make it pretty"-Knopf in einem Fotoeditor. Drücken — und fertig!
3. Lambda-Ausdrücke und LINQ: Magie für Collections
LINQ ist nicht nur bequem, es ist auch ein gutes Intro in den funktionalen Programmierstil. Die meisten LINQ-Methoden erwarten Delegates — und damit sind Lambda-Ausdrücke ihre perfekten Begleiter.
Filtern mit Where
Angenommen, wir haben wieder die Produktliste. Jetzt wollen wir nur "günstige" Produkte auswählen:
using System.Linq;
var cheapProducts = products.Where(p => p.Price < 90);
foreach (var p in cheapProducts)
Console.WriteLine(p.Name); // Tea, Milk
Wir bekommen eine neue Collection, ohne eine einzige Schleife manuell zu schreiben. Where nimmt ein Lambda-Predicate (eine Funktion, die true/false zurückgibt) und wendet es auf jedes Element an.
Sortierung mit OrderBy
Wer Ordnung mag — hier entlang:
var sorted = products.OrderBy(p => p.Price);
foreach (var p in sorted)
Console.WriteLine($"{p.Name}: {p.Price}");
// Tea: 70
// Milk: 80
// Coffee: 100
Mapping (Select) – Projektion von Daten
Manchmal braucht man nicht das ganze Objekt, sondern nur einen Teil, z.B. eine Liste von Produktnamen:
var names = products.Select(p => p.Name);
foreach (var name in names)
Console.WriteLine(name); // Coffee, Tea, Milk
Verkettungen in LINQ
LINQ ist praktisch, weil du Aufrufe hintereinander "ketten" kannst:
var namesOfCheap = products
.Where(p => p.Price < 90)
.OrderBy(p => p.Name)
.Select(p => p.Name.ToUpper());
foreach (var name in namesOfCheap)
Console.WriteLine(name); // MILK, TEA
Sieht aus wie eine Produktionslinie: jede Methode ist eine neue Verarbeitungsstufe.
Frage: Wieso sind Lambda-Ausdrücke besser als normale Methoden für LINQ?
Erstens kann man Lambdas genau dort schreiben, wo man sie braucht. Zweitens sind Lambda-Ausdrücke kurz und lesbar. Drittens: das ist Standard in modernem C# — so schreiben es alle; wer das nicht tut, hat oft Probleme bei Interviews.
4. Praktisches Beispiel
Im Verlauf des Kurses haben wir eine Lernanwendung für die Arbeit mit einem kleinen Katalog von Produkten, Benutzern oder Bestellungen gebaut. Fügen wir ihr moderne Collection-Verarbeitungsmethoden hinzu.
Suche nach Benutzername
public class User
{
public string Username { get; set; }
public int Age { get; set; }
}
var users = new List<User>
{
new User{ Username = "Alice", Age = 21 },
new User{ Username = "Bob", Age = 26 },
new User{ Username = "Charlie", Age = 32 }
};
// Suche Benutzer nach Namen
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26
Filtern nach Alter
var adults = users.Where(u => u.Age >= 18);
foreach (var u in adults)
Console.WriteLine(u.Username); // Alice, Bob, Charlie
Zählen der Benutzer
int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob und Charlie)
Überprüfung, ob alle Nutzer volljährig sind
bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True
Gibt es wenigstens einen Minderjährigen?
bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False
5. LINQ: Wie das intern funktioniert
Wenn du z.B. Where(u => u.Age > 20) schreibst, ist das im Grunde genommen das Gleiche wie eine Schleife, die alle Elemente durchläuft und die Bedingung für jedes prüft. Nur macht LINQ das unsichtbar und schön, indem es dein Predicate in ein Delegate packt.
Ohne Lambda-Ausdrücke müsstest du solche Konstrukte basteln:
public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);
Oder alte anonyme Methoden im "Oldschool"-Stil:
var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });
Alles ziemlich umständlich und langweilig. Mit Lambda — elegant und modern.
6. Delegates und Standardtypen: Func, Action, Predicate
Nicht nur LINQ liebt Lambda-Ausdrücke. Viele Standard-Collection-Methoden akzeptieren spezialisierte Delegates, zum Beispiel:
- Predicate<T> — für Methoden wie Find, Exists, RemoveAll
- Func<T, TResult> — für LINQ-Methoden, Projektionen, Berechnungen
- Action<T> — für Methoden, die etwas mit dem Element tun, aber nichts zurückgeben (ForEach)
So sieht das in der Praxis aus:
// Predicate<T>
users.RemoveAll(u => u.Age < 30); // Entfernt alle unter 30
// Func<T, TResult>
var names = users.Select(u => u.Username);
// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));
7. Spickzettel zu Collection-Methoden mit Lambda-Ausdrücken
| Methode | Was macht sie | Delegate-Typ | Beispiel-Lambda |
|---|---|---|---|
|
Filtert Elemente | |
|
|
Projiziert, transformiert | |
|
|
Sortierung nach Schlüssel | |
|
|
Erstes Element nach Bedingung | |
|
|
Gibt es mindestens ein Element nach Bedingung | |
|
|
Erfüllen alle Elemente die Bedingung | |
|
|
Anzahl der Elemente nach Bedingung | |
|
|
Etwas mit jedem Element tun | |
|
|
Entfernt alle nach Predicate | |
|
8. Typische Fehler und Besonderheiten
Einer der häufigsten Fehler ist zu vergessen, dass LINQ die ursprüngliche Collection nicht ändert, sondern eine neue Sequenz zurückgibt. Das heißt: nach dem Code var sorted = users.OrderBy(u => u.Age); bleibt die Collection users in ihrer ursprünglichen Reihenfolge! Das kann verwirren: manchmal denkt man, alles sei bereits sortiert — ist es aber nicht.
Ein weiterer Punkt: Methoden wie Where, Select und andere geben Objekte vom Typ IEnumerable<T> zurück. Das ist eine "faule" Collection — die eigentliche Verarbeitung beginnt erst, wenn du wirklich aufzählst (foreach, ToList() usw.). Wenn du das Ergebnis materialisieren willst, vergiss nicht ToList() oder ToArray() aufzurufen:
var sortedList = users.OrderBy(u => u.Age).ToList();
Außerdem: wenn ein Lambda auf Variablen außerhalb seines Gültigkeitsbereichs zugreift (Closures), dann leben diese Variablen so lange weiter, wie die Referenz auf das Lambda existiert. Nicht super schlimm, aber wenn du ein Lambda in einem langlebigen Objekt nutzt und ein "riesiges Array" darin eingefangen hast, wird dieses Array im Speicher hängen bleiben zusammen mit dem Lambda.
Und noch eins: benutze sprechende Namen für Parameter und Variablen — das erhöht die Lesbarkeit enorm, besonders wenn du mehrere verschachtelte Lambdas hast.
GO TO FULL VERSION