Menu

LINQ en C# : Where, Select, OrderBy, GroupBy et plus

LINQ ajoute des opérateurs de requête à toutes les collections C#. Apprenez la syntaxe de méthode et la syntaxe de requête, filtrer avec Where, projeter avec Select, trier avec OrderBy et ThenBy, grouper avec GroupBy, le piège First ou FirstOrDefault, les agrégats, et comment l'exécution différée change le moment où votre requête s'exécute.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

LINQ (Language Integrated Query) est une bibliothèque de méthodes pour filtrer, transformer, trier, grouper et résumer des séquences. Ajoutez using System.Linq; et chaque tableau, List<T>, Dictionary et autre IEnumerable<T> gagne des méthodes comme Where, Select et OrderBy, qui prennent chacune une lambda indiquant quoi faire d'un élément.

Une première requête : syntaxe de méthode et syntaxe de requête

LINQ a deux notations. La syntaxe de méthode enchaîne des appels ; la syntaxe de requête se lit comme du SQL. Le compilateur transforme la syntaxe de requête en ces mêmes appels de méthode, donc elles produisent des résultats identiques.

Sortie :

95, 91, 88
95, 91, 88

La syntaxe de méthode est la plus courante dans le code moderne, et certains opérateurs (Take, Distinct, Count, First, ToList) n'existent que sous forme de méthodes. La syntaxe de requête brille pour les jointures et pour les requêtes qui introduisent des variables intermédiaires avec let. La suite de cette page utilise la syntaxe de méthode et montre la syntaxe de requête là où elle apporte quelque chose.

Where et Select : filtrer et projeter

Where garde les éléments qui satisfont une condition. Select transforme chaque élément en autre chose. Ensemble, ils couvrent la plupart des requêtes du quotidien.

Sortie :

Mug, Kettle
MUG $8.50 | LAMP $32.00 | KETTLE $27.00 | RUG $120.00
Lamp: 38.40
Rug: 144.00
1. Mug 2. Lamp 3. Kettle 4. Rug

Where ne modifie jamais les éléments, et Select ne change jamais leur nombre. new { p.Name, WithTax = ... } crée un type anonyme, une classe que le compilateur écrit pour vous, dont vous lisez les propriétés avec la syntaxe à point habituelle. Il n'a pas de nom que vous puissiez écrire, donc une variable qui en contient un doit être déclarée avec var, et il convient aux résultats qui restent dans une seule méthode.

OrderBy, ThenBy et le tri sur plusieurs clés

Sortie :

Ana 60
Ana 15
Ben 90
Ben 40
Chloe 25
Ben, Ana, Ben, Chloe, Ana

OrderBy est stable : les éléments de clés égales restent dans leur ordre d'origine. Utilisez ThenBy ou ThenByDescending pour une seconde clé. Un second OrderBy retrierait tout selon sa propre clé, en ne laissant à la première qu'un rôle de départage : la priorité inverse. En syntaxe de requête, les clés sont séparées par des virgules : orderby o.Customer, o.Total descending.

Contrairement à List<T>.Sort, OrderBy laisse la source intacte et renvoie une nouvelle séquence.

First, Single, Last et leurs versions OrDefault

Ces opérateurs renvoient un seul élément au lieu d'une séquence. Les versions simples lèvent une exception quand il n'y a rien à renvoyer ; les versions OrDefault renvoient la valeur par défaut du type.

Sortie :

Ana
Ben
Bea
True
First: InvalidOperationException
Single: InvalidOperationException
0

L'exception de First sur une séquence vide est une InvalidOperationException avec le message « Sequence contains no elements » (ou « Sequence contains no matching element » quand aucun élément ne satisfait le prédicat), l'une des erreurs C# les plus recherchées. Utilisez FirstOrDefault quand « aucun » est un résultat normal, et testez null.

La dernière ligne montre le piège des types valeur : pour une séquence d'int, FirstOrDefault renvoie 0 quand rien ne correspond, ce qu'on ne peut pas distinguer d'un vrai 0. Filtrez avec Where et vérifiez d'abord Any(), ou, depuis .NET 6, passez votre propre valeur par défaut : scores.FirstOrDefault(s => s > 100, -1).

Single sert pour « exactement un » : il lève une exception s'il n'y en a aucun ou plus d'un, ce qui en fait autant une assertion qu'une requête.

Any, All, Count et Contains

Sortie :

True
True
True
3
True
True

Any() s'arrête à la première correspondance, donc items.Any() est la bonne façon de demander « y a-t-il quelque chose ? ». items.Count() > 0 lit la propriété Count quand la source est une collection comme une liste ou un tableau, mais sur une requête ou une autre séquence paresseuse, il parcourt d'abord chaque élément. All sur une séquence vide renvoie true, car aucun élément n'échoue à la condition.

Sum, Average, Min et Max

Sortie :

Total: 28.25
Items: 7
Priciest: 7.25
Average price: 4.92
0
Average: InvalidOperationException

Sum d'une séquence vide vaut 0, mais Average, Min et Max d'une séquence vide de nombres lèvent InvalidOperationException : il n'y a pas de réponse sensée. Protégez-vous avec Any(), ou utilisez d'abord DefaultIfEmpty() : empty.DefaultIfEmpty(0).Average().

Distinct, Take et Skip

Sortie :

home, shop, cart
Page 1: 1 2 3 4 5 6 7 8 9 10
Page 2: 11 12 13 14 15 16 17 18 19 20
Page 3: 21 22 23
12, 15

Distinct garde la première occurrence de chaque valeur, dans l'ordre. Skip(n).Take(m) est le schéma standard de pagination, et TakeWhile/SkipWhile s'arrêtent ou commencent au premier élément qui ne satisfait pas une condition. Enumerable.Range(start, count) génère une séquence d'entiers sans tableau.

GroupBy

GroupBy répartit les éléments dans des compartiments selon une clé. Chaque groupe a une Key et est lui-même une séquence, donc vous pouvez l'agréger.

Sortie :

North: 3 sales, total 600
South: 2 sales, total 400
East: 1 sales, total 350
Top rep: Ana 400
North -> Ana/Chloe/Ana; South -> Ben/Eli; East -> Dev

Les groupes sortent dans l'ordre où leurs clés apparaissent pour la première fois dans la source (North, South, East), pas triés ; ajoutez OrderBy(g => g.Key) si vous en avez besoin. En syntaxe de requête, group X by K choisit ce qui va dans chaque groupe (ici, seulement le nom du vendeur) et into g nomme le groupe pour que la requête puisse continuer.

ToList, ToArray et ToDictionary

Les méthodes To... exécutent la requête et stockent les résultats.

Sortie :

ben@x.com
2
ToDictionary: ArgumentException

ToDictionary lève ArgumentException quand deux éléments produisent la même clé, exactement comme Dictionary.Add. Si des doublons sont attendus, groupez d'abord (comme le fait wordCounts) ou utilisez ToLookup, qui associe chaque clé à une séquence de valeurs.

L'exécution différée

Where, Select, OrderBy, GroupBy et la plupart des autres opérateurs renvoient un objet requête et ne font aucun travail tant que quelque chose ne l'énumère pas. Cela a deux conséquences qui méritent d'être vues :

Sortie :

25, 40, 99
  converting 10
  converting 25
  converting 40
  converting 99
Count: 4
  converting 10
  converting 25
  converting 40
  converting 99
Max: 9900
  converting 10
  converting 25
  converting 40
  converting 99
Cached: 4, 9900

La première requête a vu 99 alors qu'il a été ajouté après l'écriture de la requête : le filtre s'est exécuté quand string.Join l'a énumérée. La seconde requête a affiché ses conversions deux fois, car Count() et Max() l'ont chacun énumérée depuis le début. Avec une lambda peu coûteuse, ce n'est que du travail gaspillé ; avec une requête de base de données, une lecture de fichier ou un nombre aléatoire dans la lambda, les deux passages peuvent renvoyer des données différentes. Quand vous utiliserez un résultat plus d'une fois, matérialisez-le avec ToList() ou ToArray().

Les opérateurs qui renvoient une seule valeur (Count, Sum, First, Any, Max) et les méthodes To... s'exécutent immédiatement. Voir IEnumerable et yield pour la façon dont cette paresse est construite.

Référence rapide

OpérateurFaitRenvoie
Where(x => cond)FiltrerSéquence (différée)
Select(x => expr)Transformer chaque élémentSéquence (différée)
SelectMany(x => seq)Aplatir des séquences imbriquéesSéquence (différée)
OrderBy, ThenBy (+Descending)Trier, de façon stableSéquence (différée)
GroupBy(x => key)Répartir par cléSéquence de groupes (différée)
Distinct, Take, SkipDédoublonner, paginerSéquence (différée)
First, Single, LastUn élément, exception s'il n'y en a pasÉlément
FirstOrDefault etc.Un élément ou la valeur par défautÉlément
Any, All, ContainsTesterbool
Count, Sum, Min, Max, AverageAgrégerNombre
ToList, ToArray, ToDictionaryExécuter et stockerCollection

Erreurs courantes

  • First() sur une séquence qui peut être vide. Cela lève « Sequence contains no elements » ; utilisez FirstOrDefault et testez.
  • Deux appels à OrderBy. La seconde clé devient la principale ; utilisez ThenBy.
  • Énumérer une requête plusieurs fois. Le travail se répète ; appelez ToList() une fois.
  • S'attendre à ce qu'une requête fige sa source. Elle lit la source au moment de l'énumération, pas de l'écriture.
  • Average, Min, Max sur une séquence vide. Ils lèvent une exception ; protégez-vous avec Any() ou DefaultIfEmpty.
  • Count() > 0 pour tester la présence d'éléments. Any() s'arrête au premier.

Questions fréquentes

Qu'est-ce que LINQ en C# ?

LINQ (Language Integrated Query) est un ensemble de méthodes d'extension de System.Linq, comme Where, Select, OrderBy et GroupBy, qui fonctionnent sur n'importe quel IEnumerable<T> : tableaux, listes, dictionnaires et résultats d'autres requêtes. C# a aussi une syntaxe de requête (from x in items where ... select ...) que le compilateur traduit en ces mêmes appels de méthode.

Quelle est la différence entre Select et Where dans LINQ ?

Where filtre : il garde les éléments pour lesquels la lambda renvoie true et les laisse inchangés. Select projette : il transforme chaque élément en autre chose, comme une propriété ou un nouvel objet, et garde le même nombre d'éléments. On les enchaîne souvent : people.Where(p => p.Age >= 18).Select(p => p.Name).

Quelle est la différence entre First et FirstOrDefault ?

First() renvoie le premier élément (ou la première correspondance d'un prédicat) et lève InvalidOperationException (« Sequence contains no elements » ou « no matching element ») quand il n'y en a pas. FirstOrDefault() renvoie à la place la valeur par défaut du type : null pour les classes et 0 pour int, donc vérifiez le résultat avant de l'utiliser.

Comment trier sur deux champs avec LINQ ?

Enchaînez ThenBy après OrderBy : orders.OrderBy(o => o.Customer).ThenByDescending(o => o.Total). Appeler deux fois OrderBy ne fonctionne pas : le second appel retrie toute la séquence, sa clé devient donc la clé principale et la première ne sert plus qu'à départager les égalités, l'inverse de ce qui était voulu.

Comment fonctionne GroupBy dans LINQ ?

GroupBy(x => key) renvoie un groupe par clé distincte. Chaque groupe est un IGrouping<TKey, TElement> : il a une propriété Key et est lui-même une séquence des éléments ayant cette clé, donc vous pouvez appeler Count(), Sum() ou Select dessus. Les groupes sortent dans l'ordre où leurs clés apparaissent pour la première fois.

Qu'est-ce que l'exécution différée dans LINQ ?

La plupart des opérateurs LINQ ne s'exécutent pas quand vous les appelez ; ils renvoient une requête qui s'exécute à chaque énumération (par foreach, ToList(), Count(), etc.). Une requête voit donc les modifications apportées à la source après sa définition, et l'énumérer deux fois refait le travail deux fois. Appelez ToList() ou ToArray() pour l'exécuter une fois et garder les résultats.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER