CodeGym /Kurse /C# SELF /Neue LINQ-Methoden: CountB...

Neue LINQ-Methoden: CountBy und AggregateBy

C# SELF
Level 32, Lektion 2
Verfügbar

1. Einführung

Falls du schon mal einen "klassischen" LINQ-Query mit Gruppierung geschrieben hast, zum Beispiel:


var cityCounts = students.GroupBy(s => s.City)
                         .Select(g => new { City = g.Key, Count = g.Count() });

dann hast du bestimmt gemerkt, dass der Code für einfaches Zählen pro Gruppe ziemlich umständlich aussieht. Mit .NET 9 hat das Microsoft-Team das Leben der Entwickler leichter gemacht und zwei oft gewünschte Methoden zu LINQ hinzugefügt:

  • CountBy — ein schneller Weg, um die Anzahl der Elemente pro Schlüssel zu zählen.
  • AggregateBy — ein universeller Aggregator pro Schlüssel (zählt nicht nur, sondern summiert, usw.).

Übrigens, diese Methoden sind auf Wunsch der Community entstanden, und ähnliche Features gibt es schon lange in vielen "fortgeschrittenen" LINQ-Bibliotheken wie MoreLINQ.

2. Die Methode CountBy: Kurz und knackig gruppieren

Beschreibung

CountBy ist im Grunde "Gruppieren und direkt danach zählen". Die Methode gibt eine Collection von "Schlüssel-Anzahl"-Paaren zurück, was in echten Datenanalyse-Aufgaben super oft gebraucht wird: Top-Städte, Anzahl der Studenten pro Note, Häufigkeit von irgendwas.

Signatur (vereinfacht):


IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

Praxisbeispiele

Beispiel 1: Anzahl der Studenten pro Stadt

Nehmen wir mal so eine Klasse:


class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
    public string City { get; set; }
}

Und in unserer App — eine Liste von Studenten:


var students = new List<Student>
{
    new Student { Name = "Ivan", Grade = 5, City = "Neonville" },
    new Student { Name = "Anna", Grade = 4, City = "Los-Santos" },
    new Student { Name = "Egor", Grade = 3, City = "Neonville" },
    new Student { Name = "Maria", Grade = 5, City = "Rosewater" },
    new Student { Name = "Oleg", Grade = 4, City = "Neonville" }
};

In .NET 8 und früher musste man sowas schreiben:


var group = students.GroupBy(s => s.City)
                    .Select(g => new { City = g.Key, Count = g.Count() });

foreach (var cityInfo in group)
{
    Console.WriteLine($"{cityInfo.City}: {cityInfo.Count} Studenten");
}

In .NET 9 macht das ein einziger, kurzer Aufruf:


var cityCounts = students.CountBy(s => s.City);
foreach (var (city, count) in cityCounts)
{
    Console.WriteLine($"{city}: {count} Studenten");
}

Ja, du siehst richtig — kein GroupBy mehr, kein Count() — alles easy und direkt!

Beispiel 2: Zählen nach Noten

Klassiker für jeden Dekan — wie viele Einser- und Dreier-Studenten gibt's in der Gruppe:


var gradeCounts = students.CountBy(s => s.Grade);

foreach (var (grade, count) in gradeCounts)
{
    Console.WriteLine($"Note {grade}: {count} Studenten");
}

Beispiel 3: Buchstabenhäufigkeit in einem String

CountBy geht nicht nur mit Objekten, sondern auch mit einfacheren Sachen:


string word = "supercalifragilisticexpialidocious";
var charFrequencies = word.CountBy(ch => ch);

foreach (var (letter, count) in charFrequencies)
{
    Console.WriteLine($"{letter} : {count}");
}

Wie sieht das im Speicher aus? (Illustration)

Schlüssel Count (vor CountBy) Count (mit CountBy)
"Neonville"
3 3
"Rosewater"
1 1
"Los Santos"
1 1

So ein Ansatz macht den Code lesbar und reduziert Fehler beim Gruppieren.

Typische Fehler und Besonderheiten

Denk dran: CountBy gibt ein Tuple (Key, Count) zurück, keinen anonymen Typ. Das ist manchmal weniger sprechend, aber dafür maximal transparent. Außerdem kannst du nicht direkt den Zahlentyp für Count angeben — es kommt immer ein int zurück.

3. Die Methode AggregateBy: Universelle Aggregation pro Gruppe

Beschreibung

Wenn CountBy einfach nur zählt, ist AggregateBy das Schweizer Taschenmesser! Willst du die Verkaufssumme pro Verkäufer, die höchste Note pro Klasse oder irgendwas anderes, was man mit einem Akkumulator ausdrücken kann? Genau das macht AggregateBy.

Signatur (stark vereinfacht):


IEnumerable<(TKey Key, TAccumulate Result)> AggregateBy<TSource, TKey, TAccumulate>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func)
  • keySelector — nach welchem Kriterium gruppiert wird.
  • seed — Startwert für die Akkumulation.
  • func — Funktion, wie das Ergebnis "aufgebaut" wird.

Praxisbeispiele

Beispiel 1: Notensumme pro Stadt

Statt so einem LINQ-Monster:


var sums = students.GroupBy(s => s.City)
                   .Select(g => new { City = g.Key, Sum = g.Sum(s => s.Grade) });

schreibst du einfach:


var sums = students.AggregateBy(
    s => s.City, // gruppieren nach Stadt
    0,           // Startwert
    (acc, s) => acc + s.Grade // Note addieren
);

foreach (var (city, sum) in sums)
{
    Console.WriteLine($"{city}: Notensumme = {sum}");
}

Beispiel 2: Höchste Note pro Stadt

Willst du das Maximum — einfach den Akkumulator ändern:


var maxByCity = students.AggregateBy(
    s => s.City,
    int.MinValue, // Startwert — kleinstmöglich
    (max, s) => Math.Max(max, s.Grade)
);
foreach (var (city, maxGrade) in maxByCity)
{
    Console.WriteLine($"{city}: höchste Note = {maxGrade}");
}

Beispiel 3: Namen als String sammeln

Wir aktualisieren unsere Studentenliste:


var namesByCity = students.AggregateBy(
    s => s.City,
    "", // Startstring
    (acc, s) => string.IsNullOrEmpty(acc) ? s.Name : acc + ", " + s.Name
);

foreach (var (city, names) in namesByCity)
{
    Console.WriteLine($"{city}: {names}");
}

Illustration "von innen" (Schema)


.   {Student, Student, Student, ...}
               |
               | (gruppieren nach city)
               v
["Neonville"]  ["Rosewater"]  ["Los Santos"]
      |           |               |
      |     (Akkumulation)        |
      |___________________________|
            |
            v
{("Neonville", summe), ("Rosewater", summe), ...}

Vergleich mit "alten" Methoden

Früher musste man für solche Aufgaben immer GroupBy + Select schreiben, darin dann Sum, Aggregate oder sonst was. Jetzt versteckt AggregateBy diesen "Schmerz" und macht den Code kurz und ausdrucksstark.

Praxis: Im eigenen Projekt nutzen

Unser Lernprojekt — ein Studententagebuch. Wir bauen einen Report für den Notendurchschnitt pro Stadt ein.


// Durchschnittsnote mit AggregateBy — wir akkumulieren Summe und Anzahl, dann berechnen wir den Schnitt
var avgByCity = students.AggregateBy(
    s => s.City,
    (Sum: 0, Count: 0),
    (acc, s) => (acc.Sum + s.Grade, acc.Count + 1)
).Select(x => (x.Key, Average: (double)x.Result.Sum / x.Result.Count));

foreach (var (city, avg) in avgByCity)
{
    Console.WriteLine($"{city}: Durchschnittsnote = {avg:F2}");
}

Hier akkumulieren wir Summe und Anzahl im Tuple und teilen dann Summe durch Anzahl für den Schnitt.

Besonderheiten und Stolperfallen

Wenn dein Akkumulator ein Referenztyp ist, denk dran: Innerhalb von AggregateBy wird für alle Iterationen pro Gruppe dieselbe Referenz verwendet, also nicht das Objekt mutieren, sonst beeinflusst du versehentlich das Ergebnis der ganzen Gruppe.

Und noch was: Wenn du nicht nur gruppieren, sondern auch das Ergebnis weiter transformieren willst — nutze direkt .Select. Wähle ein semantisch passendes seed (Startwert), damit du keine komischen Ergebnisse bekommst (z.B. kein 0 für Strings).

4. Vergleich: Alt vs. Neu

Aufgabe Alter LINQ Neuer LINQ .NET 9
Anzahl der Studenten pro Stadt
GroupBy + Select + Count
students.CountBy(s => s.City)
Notensumme pro Stadt
GroupBy + Select + Sum
students.AggregateBy(..., 0, ...)
Höchste Note pro Stadt
GroupBy + Select + Max
students.AggregateBy(..., ...)
Buchstabenhäufigkeit im String
GroupBy + Select + Count
word.CountBy(ch => ch)

Praktischer Nutzen für Arbeit und echte Projekte

Solche Methoden wie CountBy und AggregateBy sind beim Schreiben von Statistik-Reports, Datenaufbereitung für Visualisierung, Pivot-Tabellen und anderen Standardaufgaben echt Gold wert. Sie machen den Code kürzer, lesbarer und — das Wichtigste — zuverlässiger: Weniger manuelles "Debugging" bei Gruppierung und Aggregation, weniger Chancen, sich in der Logik zu vertun.

Im Bewerbungsgespräch für Junior/Middle zeigt das Wissen über neue LINQ-Methoden, dass du am Puls der Plattform bist, und im echten Projekt sparst du dir eigene Workarounds und nutzt elegante, verständliche Konstrukte, die jeder easy lesen und pflegen kann.

2
Aufgabe
C# SELF, Level 32, Lektion 2
Gesperrt
Bestimmung der höchsten Note pro Stadt
Bestimmung der höchsten Note pro Stadt
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION