1. Einführung
Wenn du mit Collections arbeitest, willst du oft nicht einfach nur jedes Element durchgehen und irgendwas machen, sondern so eine Art kurzes "Summary" über den ganzen Datensatz bekommen. Zum Beispiel:
- Zählen, wie viele Studenten eine Fünf bekommen haben.
- Herausfinden, wie viele Schüler es insgesamt in der Schule gibt.
- Berechnen, wie viele Punkte alle Studenten zusammen in der Prüfung geholt haben.
- Das Maximum und Minimum der Noten finden.
- Den Durchschnitt der Klasse berechnen.
Klar, das alles kann man auch mit einer normalen Schleife und einem Zähler lösen. Aber mal ehrlich: Wer hat schon Bock, zwanzig Zeilen Code für so eine Basic-Aufgabe zu schreiben?
LINQ bringt einen Satz Aggregatfunktionen mit – fertige Methoden, die eine Collection nehmen, alle Elemente durchgehen und dir das Ergebnis liefern: Summe, Durchschnitt, Maximum, Minimum und manchmal noch abgefahrenere Sachen.
Hier eine kurze "Aggregat-Tabelle":
| Methode | Was sie macht | Gibt zurück |
|---|---|---|
|
Zählt die Anzahl der Elemente | |
|
Zählt die Summe der Zahlenwerte | Hängt vom Typ der Elemente ab (, , ...) |
|
Berechnet das arithmetische Mittel | oder anderer Zahlentyp |
|
Sucht das größte Element | Element der Collection |
|
Sucht das kleinste Element | Element der Collection |
Alle diese Methoden sind Extension-Methoden für Collections, die IEnumerable<T> implementieren. Mehr dazu in der offiziellen Doku zu LINQ Aggregatmethoden.
2. Daten für die Praxis vorbereiten
Lass uns weiter mit einer einfachen App für Studentenverwaltung arbeiten. Stell dir vor, wir haben diese Klasse:
// Studentenmodell
public class Student
{
public string Name { get; set; }
public int Grade { get; set; } // Note nach Fünf-Punkte-Skala
public string Email { get; set; }
}
Und eine Liste:
// Basisliste der Studenten
List<Student> students = new List<Student>
{
new Student { Name = "Ivan", Grade = 5, Email = "ivan@example.com" },
new Student { Name = "Olga", Grade = 4, Email = "olga@example.com" },
new Student { Name = "Artem", Grade = 3, Email = "artem@example.com" },
new Student { Name = "Darya", Grade = 5, Email = "darya@example.com" },
new Student { Name = "Petr", Grade = 2, Email = "petr@example.com" }
};
3. Elemente zählen: Count()
Einfachste Statistik
Angenommen, du willst wissen, wie viele Studenten du insgesamt hast:
int totalStudents = students.Count(); // 5
Console.WriteLine($"Insgesamt Studenten: {totalStudents}");
LINQ-Magie: Easy! Die Methode Count() gibt die Anzahl der Elemente in der Collection zurück.
Zählen mit Bedingung
Und wie viele Einser-Schüler gibt's?
int excellentStudents = students.Count(s => s.Grade == 5);
Console.WriteLine($"Fünfer-Schüler: {excellentStudents}");
Hier geben wir Count eine Lambda mit – sie gibt nur die zurück, die die Bedingung erfüllen (s.Grade == 5). In LINQ ist das das gleiche wie Where(...).Count(), aber kürzer und ein bisschen effizienter.
Was, wenn die Collection leer ist?
Wenn die Collection leer ist, gibt Count einfach 0 zurück – kein Fehler, alles cool.
4. Werte summieren: Sum()
Die Summe aller Noten holen
Stell dir vor, du willst wissen, wie viele Punkte die Klasse insgesamt geholt hat:
int sumOfGrades = students.Sum(s => s.Grade); // 5+4+3+5+2 = 19
Console.WriteLine($"Summe aller Noten: {sumOfGrades}");
Sum nimmt einen Selector (Lambda-Ausdruck), der für jedes Element den Wert zurückgibt.
Summieren über eine Zahlen-Collection
Wenn du einfach eine Liste von Zahlen hast, brauchst du keinen Selector:
int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum(); // 15
Typische Fehler und Besonderheiten
Wenn die Collection leer ist, gibt Sum() für Zahlentypen 0 zurück. Aber wenn die Collection aus nullable-Typen besteht (int?, double?), dann funktioniert Sum auch korrekt: null-Werte werden ignoriert.
5. Arithmetisches Mittel: Average()
Den Klassendurchschnitt berechnen
Klassiker: Wie viel hat ein Student im Schnitt bekommen?
double averageGrade = students.Average(s => s.Grade); // (5+4+3+5+2)/5 = 3.8
Console.WriteLine($"Durchschnittsnote: {averageGrade:F2}");
Average – super praktisch für Statistiken. Achtung: Der Rückgabewert ist immer double, auch wenn die Werte ursprünglich int waren. So geht dir kein Nachkommabereich verloren.
Wenn kein einziges Element da ist
Wenn die Collection leer ist, wirft Average() eine InvalidOperationException. Das ist eine typische Falle: Wenn du nicht sicher bist, ob was drin ist, check das vorher!
if (students.Any())
Console.WriteLine(students.Average(s => s.Grade));
else
Console.WriteLine("Keine Daten für Durchschnitt!");
Durchschnitt über ein Zahlenarray
double avg = numbers.Average();
6. Maximum und Minimum: Max() und Min()
Wer ist der Star der Klasse und wer ist ganz unten?
Du willst wissen, wer die Klasse mit seinen Noten nach oben zieht und wer... naja, dem Lehrer Sorgen macht? Kein Problem:
int maxGrade = students.Max(s => s.Grade); // 5
int minGrade = students.Min(s => s.Grade); // 2
Console.WriteLine($"Maximale Note: {maxGrade}");
Console.WriteLine($"Minimale Note: {minGrade}");
Und wer ist genau dieser Held?
Manchmal ist nicht nur die Zahl wichtig, sondern auch der Name von dem, der sie geholt hat. Hol dir das Studentenobjekt mit der höchsten Note:
// Nimm den ersten Einser-Schüler nach absteigender Sortierung
var bestStudent = students.OrderByDescending(s => s.Grade).First();
Console.WriteLine($"Bester Student: {bestStudent.Name} ({bestStudent.Grade})");
In neueren .NET-Versionen gibt's auch MaxBy, damit geht das noch schöner. Ab .NET 6 ist das schon drin, und in .NET 9 gibt's noch mehr Komfort (siehe MaxBy in den Microsoft Docs). Aber auch ohne MaxBy kommst du mit Sortierung und .First() klar.
Fallen und Unterschiede
Wenn die Collection leer ist, führen Max() und Min() auch zu einer InvalidOperationException. Also: Sei darauf vorbereitet (vor allem, wenn du die Collection nicht vorher geprüft hast):
if (students.Any())
Console.WriteLine($"Maximale Note: {students.Max(s => s.Grade)}");
else
Console.WriteLine("Keine Studenten für Maximumsuche.");
7. Beispiele für "Ketten" von Aggregatmethoden
Oft benutzt man Aggregatmethoden zusammen mit Filtern und Projektionen:
// Durchschnittsnote unter den Einser-Schülern
double avgExcellent = students
.Where(s => s.Grade == 5)
.Average(s => s.Grade); // immer 5, aber als Beispiel cool
// Summe der Noten bei Studenten mit Note mindestens 4
int sumGood = students
.Where(s => s.Grade >= 4)
.Sum(s => s.Grade);
// Anzahl der einzigartigen E-Mails (zur Sicherheit)
int uniqueEmails = students
.Select(s => s.Email)
.Distinct()
.Count();
Hier spielt LINQ seine ganze Power aus: Einfaches Kombinieren von Operationen macht den Code ausdrucksstark und lesbar – das versteht sogar deine Katze (wenn sie Junior C# Developer ist).
8. Vergleich mit "Handarbeit": Warum Aggregatfunktionen nutzen?
Zum Verständnis vergleichen wir LINQ mit normalen Schleifen am Beispiel Durchschnittsnote:
Normaler Ansatz:
int sum = 0;
int count = 0;
foreach (var s in students)
{
sum += s.Grade;
count++;
}
double average = (count != 0) ? (double)sum / count : 0;
LINQ:
double average = students.Average(s => s.Grade);
Sogar die Behandlung einer leeren Collection ist einfacher!
9. Nützliche Besonderheiten
Kurz zu Performance und Implementierungsdetails
LINQ-Aggregate werden – im Gegensatz zu den meisten anderen LINQ-Operationen – sofort ausgeführt! Das heißt, wenn du Sum(), Count(), Average(), Max(), Min() aufrufst, läuft die ganze Schleife über die Collection genau in diesem Moment. Diese Methoden geben keine Collection zurück, sondern ein einzelnes Ergebnis.
Das ist wichtig: Wenn du vor dem Aggregat was Schweres gemacht hast, z.B. einen aufwändigen Filter oder eine Transformation – das passiert nur einmal, beim Aufruf des Aggregats.
Werden Aggregatmethoden vom Query-Syntax unterstützt?
Oft fragt ein Anfänger vor dem Start von LINQ-Methoden: "Kann man das alles auch im Query-Syntax schreiben?" Kurze Antwort: Der Query-Syntax selbst hat keine Schlüsselwörter für Aggregate, aber du kannst sie immer mit Method-Syntax mischen:
var avg = (from s in students where s.Grade > 3 select s.Grade).Average();
In den Klammern ist normaler Query-Syntax, danach rufst du die Aggregatmethode auf. Das macht man öfter, als man denkt!
10. Typische Fehler bei LINQ
Fehler Nr. 1: Versuch, Average(), Max() oder Min() auf eine leere Collection anzuwenden.
Wenn die Collection leer ist, geben Sum() und Count() einfach 0 zurück, aber Average(), Max() und Min() werfen eine Exception. Check vorher, ob die Collection mindestens ein Element hat.
Fehler Nr. 2: Lambda mit falscher Signatur übergeben.
Wenn du z.B. einen String statt einer Zahl an eine Aggregatfunktion (Sum, Max usw.) gibst, gibt's einen Compile-Error. Besonders leicht passiert das bei anonymen Methoden.
Fehler Nr. 3: Nicht optimale Prüfung der Elementanzahl.
Die Methode Where(...).Count() erstellt erst eine neue Collection und zählt dann die Elemente. Besser: Count(predicate) – das zählt direkt die passenden Elemente und ist schneller.
Fehler Nr. 4: Besonderheiten von nullable-Typen bei Aggregation ignorieren.
Wenn du die Summe über int? berechnest, ignoriert Sum() die null-Werte. Das ist korrekt, aber manchmal kommt dadurch ein unerwartetes Ergebnis raus, wenn du das nicht vorher bedenkst.
GO TO FULL VERSION