CodeGym /Cours /C# SELF /Expressions lambda dans les collections et LINQ

Expressions lambda dans les collections et LINQ

C# SELF
Niveau 49, Leçon 2
Disponible

1. Introduction

Dans les projets réels, presque un développeur sur deux passe une bonne partie de sa vie à travailler avec des collections : filtrer, compter, chercher, resorter, mettre, prendre — bref, les manipuler presque comme le frigo la nuit. Il faut très souvent extraire, traiter et agréger des données — que ce soit des listes d'utilisateurs, des produits dans un catalogue, des lignes de texte ou n'importe quels autres tableaux.

Presque toutes les collections modernes dans .NET supportent des méthodes fonctionnelles — comme Where, Select, Find, Any, All et autres. Leur force est dans l'universalité et le style concis : tu fournis juste un "morceau de logique" sous forme d'expression lambda, et la collection s'anime comme si tu avais lancé un nouveau moteur.

LINQ (Language Integrated Query) n'est pas juste du sucre syntaxique, c'est tout un mini-langage à l'intérieur de C# qui permet d'écrire des requêtes sur les données comme si tu utilisais SQL ou Excel. Mais en mieux : directement dans le code, avec l'autocomplétion, les types et le débogueur.

Mais toute cette magie fonctionne grâce aux délégués — et réécrire une méthode séparée à chaque fois pour filtrer un tableau devient vite fatigant. C'est là que les expressions lambda entrent en jeu comme des mini-fonctions "sur place", transformant un code lourde en quelque chose d'élégant et expressif.

2. Expressions lambda dans les méthodes standard des collections

Les expressions lambda montrent particulièrement leur utilité dans les méthodes standard des collections basées sur des délégués, comme Find, Exists, ForEach et beaucoup d'autres.

Exemple : Recherche par condition

Supposons que tu as une liste de produits :

using System;
using System.Collections.Generic;

// Notre classe produit
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

var products = new List<Product>
{
    new Product { Name = "Coffee", Price = 100 },
    new Product { Name = "Tea", Price = 70 },
    new Product { Name = "Milk", Price = 80 }
};

// Trouvons le premier produit cher (>90)
Product expensive = products.Find(p => p.Price > 90); // On utilise une lambda !
Console.WriteLine(expensive?.Name); // => Coffee

Sans lambda, il faudrait écrire une méthode séparée ou une fonction anonyme à l'ancienne. Là — une ligne, et le code se lit comme de l'anglais : "Trouve le produit où le prix est supérieur à 90".

Exemple : Vérification de la présence d'un produit

bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True (parce que "Tea" est moins cher que 75)

Exemple : Traitement de tous les éléments (ForEach)

Parfois il faut faire quelque chose pour chaque élément :

products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} euro"));

Analogies

En bref : les expressions lambda dans les collections, c'est comme le bouton "améliorer" dans un éditeur photo. Tu appuies — et voilà le résultat !

3. Expressions lambda et LINQ : de la magie pour les collections

LINQ n'est pas seulement pratique, c'est aussi une excellente introduction au style fonctionnel. La plupart des méthodes LINQ attendent des délégués en entrée — donc leur parfait compagnon, c'est l'expression lambda.

Filtrage avec Where

Reprenons la liste de produits. Maintenant filtrons les produits "bon marché" :

using System.Linq;

var cheapProducts = products.Where(p => p.Price < 90);

foreach (var p in cheapProducts)
    Console.WriteLine(p.Name); // Tea, Milk

On obtient une nouvelle collection sans écrire une seule boucle manuellement. Where prend un prédicat lambda (fonction retournant true/false) et l'applique à chaque élément.

Tri avec OrderBy

Pour les fans d'ordre :

var sorted = products.OrderBy(p => p.Price);

foreach (var p in sorted)
    Console.WriteLine($"{p.Name}: {p.Price}");
// Tea: 70
// Milk: 80
// Coffee: 100

Mapping (Select) – projection des données

Parfois on a besoin non pas de l'objet entier, mais d'une partie, par exemple la liste des noms :

var names = products.Select(p => p.Name);

foreach (var name in names)
    Console.WriteLine(name); // Coffee, Tea, Milk

Chaînage LINQ

LINQ est pratique car on peut enchaîner les appels :

var namesOfCheap = products
    .Where(p => p.Price < 90)
    .OrderBy(p => p.Name)
    .Select(p => p.Name.ToUpper());

foreach (var name in namesOfCheap)
    Console.WriteLine(name); // MOLOKO, CHAI

C'est comme une chaîne de production : chaque méthode est une nouvelle étape de traitement.

Question : Pourquoi les expressions lambda sont-elles meilleures que les méthodes normales pour LINQ ?

D'abord, tu peux écrire les lambdas directement là où elles sont nécessaires. Ensuite, les expressions lambda sont courtes et lisibles. Enfin, c'est le standard du C# moderne — tout le monde l'écrit comme ça, et ceux qui ne le font pas ont parfois du mal aux entretiens.

4. Exemple pratique

Pendant le cours on a écrit une application pédagogique pour gérer un petit catalogue de produits, d'utilisateurs ou de commandes. Ajoutons des méthodes modernes de traitement des collections.

Recherche d'un utilisateur par nom

public class User
{
    public string Username { get; set; }
    public int Age { get; set; }
}

var users = new List<User>
{
    new User{ Username = "Alice", Age = 21 },
    new User{ Username = "Bob", Age = 26 },
    new User{ Username = "Charlie", Age = 32 }
};

// Recherche d'un utilisateur par nom
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26

Filtrage par âge

var adults = users.Where(u => u.Age >= 18);

foreach (var u in adults)
    Console.WriteLine(u.Username); // Alice, Bob, Charlie

Compte des utilisateurs

int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob et Charlie)

Vérifier que tous les utilisateurs sont majeurs

bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True

Y a-t-il au moins un mineur ?

bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False

5. LINQ : comment ça marche en interne

Quand tu écris, par exemple, Where(u => u.Age > 20), en réalité c'est à peu près la même chose que de créer une boucle qui parcourt tous les éléments et vérifie la condition pour chacun. Sauf que LINQ le fait discrètement et proprement, en enveloppant ton prédicat dans un délégué.

Si les expressions lambda n'existaient pas, il faudrait construire des choses comme :

public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);

Ou même des méthodes anonymes à l'ancienne :

var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });

Tout ça c'est verbeux et triste. Avec la lambda — c'est élégant et moderne.

6. Délégués et types standards : Func, Action, Predicate

Ce n'est pas seulement LINQ qui adore les lambdas. Beaucoup de méthodes des collections standards acceptent des délégués spécialisés, par exemple :

  • Predicate<T> — pour les méthodes Find, Exists, RemoveAll
  • Func<T, TResult> — pour les méthodes LINQ, projections, calculs
  • Action<T> — pour les méthodes qui font quelque chose avec l'élément sans rien retourner (ForEach)

Voilà comment ça se présente en pratique :

// Predicate<T>
users.RemoveAll(u => u.Age < 30); // On a retiré tous ceux de moins de 30

// Func<T, TResult>
var names = users.Select(u => u.Username);

// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));

7. Aide-mémoire des méthodes de collections avec expressions lambda

Méthode Ce que ça fait Type de délégué Exemple de lambda
Where
Filtre les éléments
Func<T, bool>
p => p.Price > 100
Select
Projette, transforme
Func<T, U>
p => p.Name
OrderBy
Trier par clé
Func<T, K>
u => u.Age
FirstOrDefault
Premier élément correspondant à la condition
Func<T, bool>
u => u.Username == "Bob"
Any
Y a-t-il au moins un élément correspondant à la condition
Func<T, bool>
u => u.Age < 18
All
Tous les éléments satisfont-ils la condition
Func<T, bool>
u => u.Age >= 18
Count
Nombre d'éléments correspondant à la condition
Func<T, bool>
p => p.Price > 50
ForEach
Faire quelque chose pour chaque élément
Action<T>
u => Console.WriteLine(u.Name)
RemoveAll
Supprime tout selon le prédicat
Predicate<T>
u => u.Age < 18

8. Erreurs typiques et particularités

Une des erreurs les plus fréquentes est d'oublier que LINQ ne modifie pas la collection d'origine, il retourne une nouvelle séquence. Autrement dit, après le code var sorted = users.OrderBy(u => u.Age); la collection users restera dans son ordre initial ! Ça peut prêter à confusion : parfois on croit que tout est déjà trié — alors que non.

Autre détail : les méthodes comme Where, Select et autres retournent des objets de type IEnumerable<T>. C'est une collection "paresseuse" — le vrai traitement commence quand tu commences vraiment à énumérer (foreach, ToList(), etc.). Donc si tu veux matérialiser le résultat, n'oublie pas d'appeler ToList() ou ToArray() :

var sortedList = users.OrderBy(u => u.Age).ToList();

On se rappelle aussi : si une expression lambda fait référence à des variables en dehors de sa portée (closures), ces variables continuent de "vivre" en mémoire tant que la référence à la lambda existe. Ce n'est pas dramatique, mais si tu utilises une lambda à l'intérieur d'un objet longuement vivant et que tu as capturé un "énorme tableau", ce tableau restera en mémoire avec la lambda.

Et encore : utilise des noms parlants pour les paramètres et les variables — ça améliore beaucoup la lisibilité, surtout si tu as plusieurs niveaux de lambdas imbriquées.

2
Mission
C# SELF, niveau 49, leçon 2
Bloqué
Filtrage d'une liste en utilisant des expressions lambda
Filtrage d'une liste en utilisant des expressions lambda
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION