1. Avantages des expressions lambda
En programmation, des tâches similaires reviennent souvent. Par exemple : "filtrer la liste d'utilisateurs âgés de plus de 18 ans", "calculer la somme de tous les nombres qui satisfont une condition", "trier les produits par prix". Sans lambdas, on résolvait ces tâches en créant des méthodes séparées — ce qui crée du bruit dans le code, surtout si l'action n'est utilisée qu'à un seul endroit. Les lambdas rendent le code plus compact et plus proche de la formulation mentale de la tâche.
Les expressions lambda sont un outil standard dans de nombreux langages modernes (pas seulement C#), parce qu'elles permettent de "passer du comportement" comme valeur, que ce soit un filtre, un handler ou une fonction de transformation.
1. Concision et brièveté
Les expressions lambda permettent d'éviter de longues déclarations de méthodes superflues ou d'anciens delegats anonymes quand vous écrivez un petit morceau de fonctionnalité "à la volée". Par exemple, voici à quoi ressemblerait un filtre de liste de nombres avant l'apparition des lambdas :
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// Avant les lambdas :
List<int> evenNumbers = numbers.FindAll(delegate(int x) { return x % 2 == 0; });
// Avec une expression lambda :
List<int> evenNumbers2 = numbers.FindAll(x => x % 2 == 0);
Le résultat est le même, mais le code avec la lambda est beaucoup plus compact. Dans de grands projets, l'économie de lignes devient significative.
2. Amélioration de la lisibilité et de l'expressivité
Les lambdas permettent de se concentrer sur l'essence de l'opération en enlevant le "bruit" syntaxique. Votre code devient plus proche du langage naturel :
var adults = users.Where(user => user.Age >= 18);
Comparez cela avec la déclaration d'une méthode séparée bool IsAdult(User user), qu'il faudrait écrire juste pour ce filtre.
3. Intégration pratique avec LINQ et les API de collections
La principale puissance des lambdas est leur usage avec LINQ et les collections. Beaucoup de méthodes de collections standard et d'opérateurs LINQ attendent une fonction en paramètre (par exemple, Func<T, bool> pour filtrer). La lambda permet de déclarer la fonction nécessaire directement sur place :
var expensive = products.Where(p => p.Price > 1000);
var firstBook = books.FirstOrDefault(b => b.Title.StartsWith("C#"));
var doubled = numbers.Select(n => n * 2);
4. Capture de variables depuis la portée extérieure (closures)
Une expression lambda peut utiliser des variables déclarées à l'extérieur. Cela donne de la flexibilité et permet de créer des fonctions dynamiques à la volée :
int minAge = 18;
var filtered = users.Where(u => u.Age >= minAge); // minAge est "capturé" par la lambda
Cela ouvre des patterns intéressants pour générer des fonctions avec des "paramètres configurés".
Fait intéressant : Dans une lambda on peut non seulement lire, mais parfois modifier des variables externes, bien que cela doive être fait avec précaution — plus sur ce sujet dans la leçon sur les closures !
5. Code embarqué et contextuel
Les expressions lambda "vivent" là où elles sont utilisées, et non dispersées dans le projet parmi les méthodes. Cela rend le code conforme au principe "le maximum d'info dans un minimum d'espace".
Dans les exemples d'évolution de notre application (rappel : on développe un mini-système de gestion de livres dans une bibliothèque), supposons que nous avions la liste de livres suivante :
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public double Price { get; set; }
}
// Quelque part dans le code :
List<Book> books = new List<Book>
{
new Book { Title = "C# 9.0 in a Nutshell", Author = "Skeet", Year = 2022, Price = 350 },
new Book { Title = "CLR via C#", Author = "Richter", Year = 2019, Price = 250 },
// ...
};
// Trouver tous les livres coûtant plus de 300 :
var expensiveBooks = books.Where(b => b.Price > 300).ToList();
Vous voyez la condition de sélection directement à l'appel, sans perdre du temps à chercher des fonctions externes dans le code.
6. Utilisation comme callbacks, handlers, timers
Les lambdas conviennent parfaitement pour définir une action ponctuelle, par exemple un handler d'événement (callback) :
button.Click += (sender, args) => Console.WriteLine("Bouton cliqué !");
Les expressions lambda évitent d'écrire des méthodes séparées si le handler est très simple.
7. Extension des capacités des delegates
Avant, pour passer un comportement il fallait déclarer des méthodes nommées ; maintenant vous écrivez littéralement la fonction sur place :
Timer timer = new Timer(_ => Console.WriteLine("Tic !"), null, 0, 1000);
8. Simplification des tests et de l'injection de dépendances
Avec des lambdas, on peut facilement créer des implementations factices (mocks) pour les tests sans polluer le code principal avec des classes utilitaires temporaires. Par exemple, si un constructeur accepte un delegate, pour les tests vous fournissez simplement une lambda avec le comportement souhaité.
2. Principaux inconvénients des expressions lambda
Comme tout outil puissant, les expressions lambda ne sont pas sans défauts. Voyons quelles difficultés et limites elles peuvent engendrer.
1. Perte de lisibilité en cas de trop grande imbrication
Les lambdas sont bien tant qu'il n'y en a pas trop au même endroit. Les lambdas imbriquées ou longues rendent le code pénible à analyser :
var result = items.Select(x => x.Children.Where(y => y.Value > 10)
.Select(z => z.Name.ToUpper())
.ToList());
Ajoutez quelques niveaux supplémentaires — et bonjour la "puzzle populaire de lecture de code".
Conseil : Si une lambda fait plus de 3–4 lignes — extrayez-la dans une méthode nommée. N'ayez pas peur de paraître old-school : la lisibilité prime sur les raccourcis à la mode.
2. Difficultés pour le débogage
Les lambdas ne sont pas très amicales avec le debugger, surtout quand elles sont écrites "en une ligne" directement dans une chaîne d'appels LINQ. Il est parfois difficile de poser un point d'arrêt à l'intérieur d'une lambda ou de regarder les valeurs à une étape donnée.
Pour faciliter le débogage, on peut temporairement extraire le corps de la lambda dans une méthode nommée ou découper de longues chaînes LINQ en segments avec des variables intermédiaires.
3. Types d'arguments et de retours pas toujours évidents
Une expression lambda est souvent passée comme delegate (Func<...>, Action<...>, Predicate<T>). Parfois il est difficile de comprendre immédiatement quels doivent être les types des paramètres d'entrée et le type de retour, surtout dans des méthodes génériques.
Par exemple :
Func<int, string, double> myFunc = (a, b) => a + b.Length; // Oups ! Retournera int, alors que doit être double.
Le compilateur signalera l'erreur, mais pour un débutant il n'est pas évident de voir d'emblée qu'une lambda "ne rentre pas dans le moule".
4. Problèmes liés à la capture de variables
La capture de variables de la portée extérieure (closure) est une épée à double tranchant. Si on utilise les variables capturées sans prudence, on peut obtenir des résultats inattendus. Par exemple, dans une boucle :
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions) action();
Beaucoup s'attendent à obtenir 0 1 2, mais on obtient 3 3 3. Pourquoi ? Au moment de l'exécution de la lambda, la variable i vaut déjà 3 ! La lambda a "capturé" la variable elle-même, pas sa valeur.
C'est une erreur typique chez les débutants, détaillée dans la documentation officielle. C'est résoluble — mais demande de la prudence.
5. Perte de noms explicites et problèmes de réutilisation
Les expressions lambda sont adaptées aux actions ponctuelles. Mais si la même condition/fonction est utilisée à plusieurs endroits, il vaut mieux extraire la logique dans une méthode nommée. Sinon on risque la duplication et des erreurs lors de modifications.
6. Inconfort pour ajouter des commentaires XML
On ne peut pas documenter une lambda avec des commentaires XML pour la génération automatique de documentation (comme pour les méthodes). Il faut commenter les lambdas avec des commentaires ordinaires dans le corps du code.
7. Problèmes potentiels de performance
La plupart du temps, les lambdas ne sont pas sensiblement plus lentes que les méthodes normales. Cependant, la création fréquente et massive de lambdas avec capture de variables provoque l'allocation d'objets supplémentaires (closure). Dans des zones critiques en performance (par ex. en tight loop ou dans des services très chargés) il vaut réfléchir — n'est-il pas moins coûteux d'utiliser des méthodes statiques.
8. Impossible d'utiliser les opérateurs goto, break, continue hors des boucles
Si une lambda est déclarée à l'intérieur d'une boucle, on ne peut pas utiliser directement break ou continue à l'intérieur d'elle pour affecter la boucle externe — ce n'est pas autorisé syntaxiquement.
9. Une lambda ne peut pas décrire tout le comportement
Les lambdas ne peuvent pas travailler directement avec les attributes, on ne peut pas spécifier de modificateurs d'accès, et elles ne peuvent pas accomplir certaines actions spéciales — par exemple déclarer des fonctions locales nommées.
3. Choix entre deux maux
Quand les expressions lambda sont utiles
| Scénario | Lambda — pratique ? | Pourquoi |
|---|---|---|
| Filtrage / transformation courte | 👍 | Rapide et clair |
| Opérations multi-niveaux imbriquées | 👎 | Devient illisible |
| Re-use (réutilisation) | 👎 | Mieux extraire dans une méthode |
| Logique de callback, événements | 👍 | Compact |
| Description d'une logique métier complexe | 👎 | Il faut un nom + des commentaires |
| Travail avec LINQ | 👍 | Scénario idéal |
Quand il vaut mieux renoncer aux lambdas
- Si la logique est longue et contient beaucoup de branchements / calculs.
- Si la lambda fait quelque chose d'obscur pour le lecteur et n'a pas d'explication.
- Si la fonction doit être documentée, utilisée à plusieurs endroits ou avoir un nom "parlant".
- Si la lambda est utilisée trop profondément dans des appels imbriqués — risque de perdre la lisibilité.
4. Erreurs typiques en travaillant avec les expressions lambda
Erreur de capture de variables dans une boucle :
List<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act(); // Tous afficheront 5 !
Comment faire correctement :
for (int i = 0; i < 5; i++)
{
int captured = i; // on capture une variable séparée
actions.Add(() => Console.WriteLine(captured));
}
Lambda trop longue :
books.Where(b => b.Price > 1000 && b.Title.Contains("C#") && b.Author.Length > 4 && bla-bla-bla...);
// Le code devient illisible, extrayez dans une méthode !
Utiliser une lambda là où une méthode documentée est nécessaire :
Si la fonction est utilisée plusieurs fois ou doit être expliquée en détail, il vaut mieux écrire une méthode nommée :
bool IsExpensiveBook(Book book) => book.Price > 1000;
books.Where(IsExpensiveBook);
GO TO FULL VERSION