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 |
|---|---|---|---|
|
Filtre les éléments | |
|
|
Projette, transforme | |
|
|
Trier par clé | |
|
|
Premier élément correspondant à la condition | |
|
|
Y a-t-il au moins un élément correspondant à la condition | |
|
|
Tous les éléments satisfont-ils la condition | |
|
|
Nombre d'éléments correspondant à la condition | |
|
|
Faire quelque chose pour chaque élément | |
|
|
Supprime tout selon le prédicat | |
|
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.
GO TO FULL VERSION