Select et SelectMany en C# : quelle est la différence et comment cela affecte-t-il la vitesse ?
Si vous avez déjà écrit des requêtes LINQ en C#, vous avez sûrement rencontré deux méthodes similaires mais très différentes : Select et SelectMany. À première vue, les deux "sélectionnent quelque chose" dans une collection. Mais le diable, comme toujours, se cache dans les détails. Un mauvais choix entre eux peut transformer votre code rapide en un mélange lent, tandis qu'un bon choix peut le rendre élégant et fulgurant.
Dans cet article, nous ne nous contenterons pas d'analyser la théorie. Nous allons réaliser un véritable test de performance, examiner le fonctionnement interne des deux méthodes et vous donner un algorithme clair : quand utiliser Select et quand utiliser SelectMany. Prêts ? C'est parti !
Petit rappel : que sont Select et SelectMany ?
Pour commencer, les bases. Imaginez que vous ayez une liste d'étudiants (List<Student>), et que chaque étudiant ait une liste de notes (List<int>).
Select est une projection "un à un". Pour chaque élément de la collection source, vous obtenez exactement un élément en sortie. Si vous avez 10 étudiants, Select retournera une collection de 10 éléments.
- Exemple : Obtenir les noms de tous les étudiants.
- Signature :
IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
SelectMany est une projection "un à plusieurs" suivie d'un "aplatissement" (flatten). Chaque élément de la collection source peut générer plusieurs éléments en sortie, et tous sont fusionnés en une seule liste plate.
- Exemple : Obtenir toutes les notes de tous les étudiants (une seule liste plate de nombres).
- Signature :
IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
La différence semble évidente. Mais où est le piège ? Le piège réside dans la façon dont ces méthodes fonctionnent sous le capot et comment les débutants les confondent souvent.
La principale différence : la forme des données de sortie
Fixons cela tout de suite avec un exemple de code. Voici notre classe modèle :
public class Student{ public string Name { get; set; } public List<int> Grades { get; set; } = new List<int>();}
// Création des donnéesvar students = new List<Student>{ new Student { Name = "Anna", Grades = new List<int>{ 5, 4, 5 } }, new Student { Name = "Boris", Grades = new List<int>{ 3, 4 } }, new Student { Name = "Vika", Grades = new List<int>{ 5, 5, 5, 4 } }};Exemple 1 : Utilisation de Select (incorrect)
// Nous voulons obtenir toutes les notes. Mais Select retournera une collection de collections !var allGradesNested = students.Select(s => s.Grades);// Type : IEnumerable<List<int>> — trois listes à l'intérieur !
foreach (var gradeList in allGradesNested){ foreach (var grade in gradeList) { Console.Write(grade + " "); // Obligé d'utiliser une boucle imbriquée }}// Sortie : 5 4 5 3 4 5 5 5 4Cela fonctionne, mais ce n'est pas pratique. Vous obtenez une structure imbriquée que vous devez ensuite aplatir.
Exemple 2 : Utilisation de SelectMany (correct)
// SelectMany fait tout le travail pour nous : prend les collections à l'intérieur et les "aplatit"var allGradesFlat = students.SelectMany(s => s.Grades);// Type : IEnumerable<int> — une seule liste plate de nombres !
foreach (var grade in allGradesFlat){ Console.Write(grade + " "); // Pas de boucles imbriquées !}// Sortie : 5 4 5 3 4 5 5 5 4Vous sentez la différence