1. Intro
Si t’as déjà écrit une requête LINQ "classique" avec un groupement, genre :
var cityCounts = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Count = g.Count() });
t’as sûrement remarqué que pour juste compter par groupe, le code est un peu verbeux. Avec .NET 9, l’équipe Microsoft a voulu nous simplifier la vie et a ajouté deux méthodes super demandées dans LINQ :
- CountBy — un moyen rapide de compter les éléments par clé.
- AggregateBy — un agrégateur universel par clé (ça compte, ça fait la somme, etc.).
Au fait, ces méthodes sont là parce que la commu les réclamait à fond, et leurs équivalents existent depuis longtemps dans plein de libs LINQ "avancées", genre MoreLINQ.
2. La méthode CountBy : compter par groupe, easy
Description
CountBy, c’est littéralement "je groupe, puis je compte direct". Cette méthode te renvoie une collection de paires "clé-nombre", ce qui est super utile dans la vraie vie : top des villes, nombre d’étudiants par note, fréquence d’apparition de trucs, etc.
Signature (en mode simple) :
IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
Exemples concrets
Exemple 1 : Nombre d’étudiants par ville
Imaginons qu’on a cette classe :
class Student
{
public string Name { get; set; }
public int Grade { get; set; }
public string City { get; set; }
}
Et dans notre appli — une liste d’étudiants :
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" }
};
En .NET 8 et avant, fallait écrire :
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} étudiants");
}
En .NET 9, un seul appel suffit :
var cityCounts = students.CountBy(s => s.City);
foreach (var (city, count) in cityCounts)
{
Console.WriteLine($"{city}: {count} étudiants");
}
Ouais, t’as bien vu — pas de GroupBy à la main, pas de Count() à calculer — c’est direct et clair !
Exemple 2 : Compter par note
Le truc classique pour un doyen — savoir combien de cracks et de cancres il y a dans le groupe :
var gradeCounts = students.CountBy(s => s.Grade);
foreach (var (grade, count) in gradeCounts)
{
Console.WriteLine($"Note {grade}: {count} étudiants");
}
Exemple 3 : Fréquence des caractères dans une string
CountBy marche pas que sur des objets, mais aussi sur des trucs plus simples :
string word = "supercalifragilisticexpialidocious";
var charFrequencies = word.CountBy(ch => ch);
foreach (var (letter, count) in charFrequencies)
{
Console.WriteLine($"{letter} : {count}");
}
Visu mémoire (illustration)
| Clé | Count (avant CountBy) | Count (avec CountBy) |
|---|---|---|
|
3 | 3 |
|
1 | 1 |
|
1 | 1 |
Ce genre d’approche rend le code lisible et limite les bugs quand tu groupes.
Pièges et subtilités
N’oublie pas, CountBy te renvoie un tuple (Key, Count), pas un type anonyme. Parfois c’est un peu moins parlant, mais au moins c’est super clair. Aussi, tu peux pas choisir le type du nombre pour Count — c’est toujours un int.
3. La méthode AggregateBy : l’agrégateur universel
Description
Si CountBy c’est juste compter par clé, AggregateBy c’est le couteau suisse ! Tu veux la somme des ventes par vendeur, la note max par classe, ou n’importe quoi qui s’accumule ? AggregateBy fait tout ça.
Signature (en mode très simplifié) :
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 — sur quoi tu groupes.
- seed — la valeur de départ pour l’accumulation.
- func — la fonction qui dit comment "accumuler" le résultat.
Exemples concrets
Exemple 1 : Somme des notes par ville
Au lieu d’un LINQ bien lourd :
var sums = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Sum = g.Sum(s => s.Grade) });
Tu fais :
var sums = students.AggregateBy(
s => s.City, // on groupe par ville
0, // somme de départ
(acc, s) => acc + s.Grade // on ajoute la note
);
foreach (var (city, sum) in sums)
{
Console.WriteLine($"{city}: somme des notes = {sum}");
}
Exemple 2 : Note max par ville
Tu veux la valeur max ? Change juste l’accumulateur :
var maxByCity = students.AggregateBy(
s => s.City,
int.MinValue, // valeur de départ — le minimum possible
(max, s) => Math.Max(max, s.Grade)
);
foreach (var (city, maxGrade) in maxByCity)
{
Console.WriteLine($"{city}: note max = {maxGrade}");
}
Exemple 3 : On concatène les noms dans une string
On met à jour notre liste d’étudiants :
var namesByCity = students.AggregateBy(
s => s.City,
"", // string de départ
(acc, s) => string.IsNullOrEmpty(acc) ? s.Name : acc + ", " + s.Name
);
foreach (var (city, names) in namesByCity)
{
Console.WriteLine($"{city}: {names}");
}
Visu "sous le capot" (schéma)
. {Student, Student, Student, ...}
|
| (on groupe par city)
v
["Neonville"] ["Rosewater"] ["Los Santos"]
| | |
| (accumulation) |
|___________________________|
|
v
{("Neonville", somme), ("Rosewater", somme), ...}
Comparé à l’ancienne méthode
Avant, pour tout ça, fallait faire GroupBy + Select, avec dedans un Sum, Aggregate ou un autre truc tordu. Maintenant AggregateBy cache toute cette "douleur" et rend le code court et expressif.
Pratique : on l’utilise dans l’appli
Notre projet d’école — un carnet de notes étudiants. On va ajouter un rapport sur la moyenne par ville.
// On calcule la moyenne avec AggregateBy — on accumule la somme et le nombre, puis on fait la moyenne
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}: moyenne = {avg:F2}");
}
Ici on accumule la somme et le nombre dans un tuple, puis on divise pour avoir la moyenne.
Spécificités et pièges
Si ton accumulateur est un type référence, fais gaffe : dans AggregateBy c’est la même référence pour toutes les itérations du groupe, donc évite de muter l’objet, sinon tu risques de pourrir le résultat du groupe.
Et aussi : si tu veux pas juste grouper mais aussi transformer le résultat — tu peux direct utiliser .Select. Choisis bien ton seed (valeur de départ), sinon tu risques des surprises (genre, évite 0 pour les strings).
4. Comparaison ancienne vs nouvelle méthode
| Tâche | LINQ old school | Nouveau LINQ .NET 9 |
|---|---|---|
| Nombre d’étudiants par ville | |
|
| Somme des notes par ville | |
|
| Note max par ville | |
|
| Fréquence des caractères dans une string | |
|
Utilité concrète pour le taf et les vrais projets
Des méthodes comme CountBy et AggregateBy deviennent incontournables pour faire des stats, préparer des données pour des graphs, faire des tableaux croisés et tout ce genre de trucs. Ça réduit le code, c’est plus lisible et surtout plus fiable : moins de bidouilles à la main pour grouper et agréger, moins de risques de se planter dans la logique.
En entretien pour un poste Junior/Middle, connaître ces nouvelles méthodes LINQ montre que tu suis l’actu de la plateforme, et en prod — ça t’évite de réinventer la roue, tu utilises des trucs propres et faciles à relire et à maintenir.
GO TO FULL VERSION