1. Einführung
Filtern bedeutet, aus einer Kollektion nur die Elemente auszuwählen, die einer bestimmten Bedingung entsprechen. Stell dir vor: In deiner Datenbank gibt es 10 000 Produkte, aber der Manager braucht „nur“ 12, die einem neuen, cleveren Kriterium entsprechen. Jedes Mal per Hand eine Schleife und zig Checks zu schreiben, macht echt keinen Spaß und der Code wird unlesbar. LINQ nimmt dir diesen Stress ab.
In echten Apps ist Filtern die häufigste Datenoperation: Wir zeigen dem User nur die Einträge, die ihn interessieren, „säubern“ Kollektionen von unerwünschten Elementen, machen Abfragen für Reports, Suche oder E-Mail-Versand. Bei Bewerbungsgesprächen kommen LINQ-Filter-Fragen ständig dran. Das Thema zu verstehen ist ein Muss für jeden .NET-Einsteiger.
2. Wir lernen die Methode Where kennen
Sinn und Funktionsweise
Die Methode Where ist eine LINQ-Extension-Method, die eine Funktion (oder ein Lambda-Expression) nimmt, die die Filterbedingung definiert. Sie gibt eine neue Sequenz zurück (ohne das Original zu verändern!), in der nur die Elemente sind, für die die Bedingung true zurückgibt.
Die Hauptsignatur:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
Keine Angst vor generics und komischen Begriffen. Wichtig ist erstmal die Grundidee: Where nimmt die Ausgangskollektion und eine Bedingung, die du vorgibst. In der Praxis ist das easy:
- source — die Kollektion, die wir filtern (z.B. List<Product>)
- predicate — Funktion/Bedingung (z.B. p => p.Price > 100)
Eine einfache Aufgabe zum Einstieg
Angenommen, wir haben eine Produktliste und wollen nur die auswählen, deren Preis größer als 100 ist. Früher hätte man eine foreach-Schleife geschrieben und die passenden Elemente manuell in eine neue Liste gepackt. Mit LINQ geht das in einer Zeile.
3. Filtern
Unsere Klasse Product und die Startkollektion
Im letzten Modul haben wir ungefähr so eine Produktklasse geschrieben:
class Product
{
public string Name { get; set; }
public double Price { get; set; }
// Für hübsche Konsolenausgabe:
public override string ToString()
{
return $"{Name} (Preis: {Price})";
}
}
Angenommen, wir haben eine Produktliste:
var products = new List<Product>
{
new Product { Name = "Brot", Price = 30 },
new Product { Name = "Milch", Price = 87 },
new Product { Name = "Käse", Price = 250 },
new Product { Name = "Schokolade", Price = 130 }
};
Produkte mit Preis über 100 filtern mit Where
var expensiveProducts = products.Where(p => p.Price > 100);
foreach (var product in expensiveProducts)
{
Console.WriteLine(product);
}
Was passiert unter der Haube:
- Where geht alle Elemente der Liste products durch,
- ruft für jedes die Funktion auf (hier: p => p.Price > 100),
- wenn die Funktion true zurückgibt, kommt das Produkt in die neue Kollektion.
Ergebnis auf dem Bildschirm:
Käse (Preis: 250)
Schokolade (Preis: 130)
Achte drauf: Die Ursprungsliste products bleibt unverändert! LINQ zerstört deine Daten nicht.
4. Lazy Filtering (Deferred Execution)
Ein spannender und wichtiger Punkt: Das Ergebnis von Where wird erst berechnet, wenn du wirklich auf die Elemente zugreifst. Zum Beispiel, sobald die foreach-Schleife startet, „geht“ LINQ die Ursprungskollektion durch und filtert die Elemente on the fly. Wenn du das Ergebnis nicht nutzt, passiert gar nichts. Das nennt man verzögerte Ausführung (deferred execution).
Das bringt uns viele Vorteile:
- Du kannst richtig lange Method-Chains bauen (z.B. filtern und sortieren) und erst beim ersten echten Zugriff wird alles ausgeführt.
- Speicher sparen: Es gibt keine unnötigen Zwischenkollektionen.
- Bei Bedarf kannst du die Ausführung abbrechen oder nach einer bestimmten Anzahl gefundener Elemente stoppen.
Visualisierung
Ursprungskollektion —► Where (Bedingung) —► Wir gehen nur die passenden Elemente durch
[ x ] [ x ] [ + ] [ + ] —► p.Price > 100 —► [ + ] [ + ]
(x — Element erfüllt die Bedingung nicht, + — erfüllt sie)
5. Syntax-Varianten
Klassische Extension-Method (Method Syntax)
Diesen Stil nutzen wir schon:
var result = products.Where(p => p.Price > 100);
Alternative: Query Syntax
LINQ unterstützt auch eine Syntax, die wie SQL-Queries aussieht:
var result = from p in products
where p.Price > 100
select p;
Das Ergebnis ist das gleiche!
Welche Syntax du nimmst, ist Geschmackssache, aber method syntax (Punkt-Methoden) sieht man öfter, vor allem in echten Projekten und bei langen Method-Chains.
6. Komplexe Filterbedingungen
Mehrere Bedingungen
Du kannst logische Operatoren nutzen (&&, ||, !):
// Finde alle teuren Produkte außer "Schokolade"
var filtered = products.Where(
p => p.Price > 100 && p.Name != "Schokolade"
);
Filtern nach String (Substring, Groß-/Kleinschreibung, Contains)
// Produkte, deren Name den Buchstaben "l" enthält
var lProducts = products.Where(p => p.Name.Contains("l"));
Achtung: Contains ist case-sensitiv! Wenn du „ohne Groß-/Kleinschreibung“ willst, geht das so:
var lProducts = products.Where(p => p.Name
.ToLower().Contains("l")); // jetzt kommen "Milch" und "Schokolade" beide ins Ergebnis
Filtern über mehrere Kollektionen
Angenommen, du hast zwei Listen – Produkte und Zahlungen. Du kannst Produkte filtern, die in der Zahlungsliste vorkommen – dafür nutzt man aber besser Any und Join, dazu später mehr. Merke dir: LINQ kann auch noch viel komplexer filtern!
7. Nützliche Feinheiten
Verschachteltes Filtern und Method-Chains
Du kannst mehrere Filterstufen kombinieren, indem du Where mehrfach hintereinander aufrufst:
var filtered = products
.Where(p => p.Price > 100)
.Where(p => p.Name.StartsWith("Sch"));
Aber besser ist es, die Bedingungen in einem Where mit logischen Operatoren zu verbinden – das ist effizienter und der Code bleibt übersichtlicher.
Eingebaute Vergleicher und eigene Funktionen
Manchmal reichen die Standard-Operatoren nicht. Zum Beispiel willst du Strings ohne Groß-/Kleinschreibung und mit Culture beachten. Dann sind Vergleichsmethoden praktisch:
var rusProducts = products.Where(
p => p.Name.StartsWith("sch", StringComparison.OrdinalIgnoreCase)
);
8. Filtern und Benutzereingaben
Probieren wir mal ein simples Filter-Command in der Praxis! Zum Beispiel fragen wir den User nach dem Mindestpreis und zeigen dann alle passenden Produkte.
Console.Write("Mindestpreis für das Produkt? ");
var minPriceStr = Console.ReadLine();
if (double.TryParse(minPriceStr, out double minPrice))
{
var filtered = products.Where(p => p.Price >= minPrice);
foreach (var product in filtered)
{
Console.WriteLine(product);
}
}
else
{
Console.WriteLine("Fehler: Keine Zahl eingegeben.");
}
Jetzt sieht unsere "Mini-App" schon fast wie ein Shop aus!
9. Typische Fehler und Stolperfallen bei Where
Beim Programmieren gibt’s wie im echten Leben Fallstricke. Das solltest du vermeiden.
Vergessen, ToList() oder ToArray() zu rufen
Das Ergebnis von Where ist kein List und auch kein Array! Es ist ein IEnumerable<Product>. Meist klappt das super in der foreach-Schleife, aber wenn du wirklich eine Kollektion brauchst (z.B. für Indexzugriff), vergiss nicht .ToList() oder .ToArray() zu rufen:
var filteredList = products.Where(p => p.Price > 100).ToList();
Wenn du das vergisst, bekommst du schnell Fehler wie "Collection was modified during enumeration" – vor allem, wenn du die Ursprungskollektion während der Iteration änderst.
Ändern der Ursprungskollektion
Da LINQ-Filterung verzögert ist, kann es zu Exceptions kommen, wenn du während der Iteration Elemente aus der Ursprungsliste entfernst. Besonders bei Multithreading oder wenn die Kollektion direkt beim Filtern geändert wird.
Null-Werte und Prädikate
Wenn die Kollektion null enthält und du im Prädikat auf ein Feld zugreifst, ohne zu prüfen, gibt’s ein NullReferenceException. Zum Beispiel:
var filtered = products.Where(p => p.Name.StartsWith("A"));
Wenn ein Element in products null ist, crasht der Code.
Tipp: Immer auf null prüfen, wenn das vorkommen kann:
var filtered = products.Where(p => p != null && p.Name.StartsWith("A"));
GO TO FULL VERSION