CodeGym /Kurse /C# SELF /Lambda-Ausdrücke in Collections und

Lambda-Ausdrücke in Collections und LINQ

C# SELF
Level 49, Lektion 2
Verfügbar

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
Where
Filtert Elemente
Func<T, bool>
p => p.Price > 100
Select
Projiziert, transformiert
Func<T, U>
p => p.Name
OrderBy
Sortierung nach Schlüssel
Func<T, K>
u => u.Age
FirstOrDefault
Erstes Element nach Bedingung
Func<T, bool>
u => u.Username == "Bob"
Any
Gibt es mindestens ein Element nach Bedingung
Func<T, bool>
u => u.Age < 18
All
Erfüllen alle Elemente die Bedingung
Func<T, bool>
u => u.Age >= 18
Count
Anzahl der Elemente nach Bedingung
Func<T, bool>
p => p.Price > 50
ForEach
Etwas mit jedem Element tun
Action<T>
u => Console.WriteLine(u.Name)
RemoveAll
Entfernt alle nach Predicate
Predicate<T>
u => u.Age < 18

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.

2
Aufgabe
C# SELF, Level 49, Lektion 2
Gesperrt
Filtern einer Liste mit Lambda-Ausdrücken
Filtern einer Liste mit Lambda-Ausdrücken
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION