1. Introduction
Un délégué — c'est un type qui décrit la « signature » d'une fonction : quels paramètres elle prend, ce qu'elle retourne. C'est comme le passeport d'une fonction ou le pass pour une boîte de nuit pour le code : si la signature correspond — tu passes, sinon — désolé, on te laisse dehors.
Exemple :
public delegate int Operation(int a, int b);
Ce délégué décrit une fonction qui prend deux int et retourne un int. Voilà à quoi ressemblait la programmation « avant les lambdas » (sympa, mais pas très pratique) :
Operation add = delegate (int x, int y)
{
return x + y;
};
Avec l'arrivée des lambdas le code devient plus concis :
Operation add = (x, y) => x + y;
Fait intéressant : en C# une expression lambda est juste une manière de créer une « fonction anonyme », qui est automatiquement compilée en une instance d'un délégué du type approprié. Et là la frontière entre les deux devient quasiment imperceptible !
Délégués intégrés : Func, Action, Predicate
Pour ne pas inventer une infinité de délégués personnalisés à chaque fois, C# propose des types-fonctions « génériques » :
- Func<T1, T2, ..., TResult> — un délégué générique qui prend des paramètres (T1, T2, ...) et retourne un résultat de type TResult.
- Action<T1, T2, ...> — presque pareil, mais ne retourne rien (void).
- Predicate<T> — un délégué qui prend un seul paramètre et retourne un bool (le chouchou de tous les « filtres »).
| Délégué | Type retourné | Exemple de syntaxe |
|---|---|---|
|
|
|
|
|
|
|
|
|
Exemple :
Func<int, int, int> add = (a, b) => a + b;
Action<string> show = s => Console.WriteLine(s);
Predicate<int> isEven = n => n % 2 == 0;
2. Expressions lambda « dans l'enveloppe » d'un délégué
Passer une lambda à une méthode qui attend un délégué
Toute méthode qui prend un délégué peut « avaler » une lambda, si sa signature correspond à celle attendue.
public static void CalculateAndShow(int a, int b, Func<int, int, int> operation)
{
int result = operation(a, b);
Console.WriteLine($"Résultat : {result}");
}
// On appelle avec une lambda
CalculateAndShow(5, 7, (x, y) => x * y); // Résultat : 35
Exemple avec Action
void ProcessString(string message, Action<string> processor)
{
processor(message);
}
// On utilise une lambda
ProcessString("Salut, monde!", s => Console.WriteLine(s.ToUpper()));
Exemple avec Predicate
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);
À ces moments-là, on comprend : pourquoi inventer des délégués maison, quand on a la roue testée et approuvée de Microsoft.
Comment fonctionnent ensemble les délégués et les lambdas
flowchart TD
A[Expression lambda] -->|Crée| B((Délégué))
B -->|Est passé comme argument| C[Méthode]
B -->|Est stocké| D[Propriété/Champ/Collection]
C -->|Appelle| E[Code de la lambda]
3. Passer une lambda comme délégué dans vos propres méthodes
Si votre méthode utilise des délégués, la lambda rend l'utilisation super flexible.
Exemple : notre mini-calculatrice
// On définit une méthode pour travailler avec des opérations
static int PerformOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}
// On applique différentes opérations (les lambdas sont de la partie !)
int sum = PerformOperation(3, 4, (x, y) => x + y);
int multiply = PerformOperation(3, 4, (x, y) => x * y);
int max = PerformOperation(3, 4, (x, y) => x > y ? x : y);
Console.WriteLine(sum); // 7
Console.WriteLine(multiply); // 12
Console.WriteLine(max); // 4
Tu vois comme la logique change élégamment, au lieu d'avoir trois méthodes séparées ?
4. Lambdas + délégués dans LINQ (et pas seulement)
Dans LINQ les méthodes attendent une « mini-fonction logique », c'est-à-dire justement un délégué. Presque toutes les méthodes LINQ standard prennent ces types :
- Func<T, bool> pour le filtrage (Where)
- Func<T, TResult> pour les transformations (Select)
- Func<T, TKey> pour les tris (OrderBy)
List<string> names = new List<string> { "Anna", "Oleg", "Viktor", "Yana" };
// On obtient seulement les noms de plus de 3 caractères
var longNames = names.Where(name => name.Length > 3);
// On transforme les noms en majuscules
var upperNames = names.Select(name => name.ToUpper());
LINQ — c'est des « lambdas à fond », parce que tout ce qu'on peut passer comme délégué, on peut le passer comme lambda !
5. Combinaison : stockage et passage de délégués-lambdas
Liste de fonctions
Une lambda n'est pas juste une fonction anonyme temporaire. Tu peux stocker un ensemble de lambdas dans une collection et les utiliser dynamiquement.
List<Func<int, int, int>> operations = new List<Func<int, int, int>>
{
(x, y) => x + y,
(x, y) => x - y,
(x, y) => x * y,
(x, y) => x / y
};
foreach (var op in operations)
{
Console.WriteLine(op(10, 2));
}
Cette technique revient souvent dans les tests, les handlers et les plugins.
Dictionnaire de délégués
Et si tu es fan d'architecture plug-and-play :
var mathFuncs = new Dictionary<string, Func<int, int, int>>
{
{ "plus", (x, y) => x + y },
{ "minus", (x, y) => x - y },
{ "pow", (x, y) => (int)Math.Pow(x, y) }
};
string command = "pow"; // Simulation de saisie utilisateur
if (mathFuncs.TryGetValue(command, out var operation))
{
Console.WriteLine(operation(2, 5)); // 32
}
else
{
Console.WriteLine("Commande introuvable");
}
6. Petites astuces utiles
Lambda-délegués : retourner un délégué depuis une méthode
On peut retourner des délégués (et donc des lambdas) depuis des méthodes — ça devient presque une fabrique de fonctions !
Func<int, int> GetMultiplier(int factor)
{
// On retourne un délégué lambda qui multiplie par factor
return x => x * factor;
}
var triple = GetMultiplier(3);
Console.WriteLine(triple(5)); // 15
var quadruple = GetMultiplier(4);
Console.WriteLine(quadruple(5)); // 20
Voilà comment un closure devient un vrai outil, pas juste un gadget !
Composer des fonctions : Combine, Delegate.Combine et les multicast delegates
Parfois on veut qu'un seul « appel » déclenche plusieurs fonctions. Par exemple, gestion d'événements GUI : on clique sur un bouton — et plusieurs handlers réagissent.
Action et les délégués permettent de « chaîner » plusieurs fonctions :
Action<string> pipeline = s => Console.WriteLine("Étape 1 : " + s);
pipeline += s => Console.WriteLine("Étape 2 : " + s.ToUpper());
pipeline += s => Console.WriteLine("Étape 3 : " + s.Length);
pipeline("test");
// Affiche :
// Étape 1 : test
// Étape 2 : TEST
// Étape 3 : 4
Pratique pour des scénarios en chaîne ou la gestion d'événements.
Important : Pour les fonctions qui retournent une valeur, le résultat ne viendra que du dernier appel (la « queue » de la fonction). Pour Action — toutes seront exécutées, vu que c'est du void.
Utiliser des lambda-délegués comme callbacks
Scénario classique — « appelle-moi quand tu as fini ».
void DownloadFile(string url, Action<string> onFinish)
{
// Simulation du téléchargement...
System.Threading.Thread.Sleep(500);// (Simulation d'un travail long, n'utilisez pas dans de vraies applications GUI)
onFinish($"Téléchargement '{url}' terminé !");
}
DownloadFile("http://example.com", msg => Console.WriteLine(msg));
Dans de vraies applis (API, bases de données, fichiers) — c'est un pattern courant.
Fonction d'ordre supérieur : exemple de filtre par prédicat
Les expressions lambda et les délégués permettent d'écrire des fonctions génériques qui acceptent d'autres fonctions en paramètres. C'est la base du pattern « fonction d'ordre supérieur ».
List<int> Filter(List<int> source, Predicate<int> condition)
{
List<int> result = new List<int>();
foreach (var item in source)
if (condition(item)) result.Add(item);
return result;
}
// On utilise différentes lambdas :
var onlyPositive = Filter(new List<int> { -2, 0, 2, 7 }, x => x > 0);
var onlyEven = Filter(new List<int> { -2, 0, 2, 7 }, x => x % 2 == 0);
On peut même créer des « usines » de lambdas :
Func<int, Predicate<int>> GetRangeChecker(int min, int max) =>
x => x >= min && x <= max;
Predicate<int> inRange = GetRangeChecker(1, 10);
var filtered = Filter(new List<int> { 0, 5, 10, 15 }, inRange); // 5, 10
7. Erreurs typiques et subtilités de la combinaison
Si la signature des paramètres ne correspond pas, le compilateur râlera (CS1660 : "Cannot convert lambda expression").
Une expression lambda peut surprendre en « se souvenant » de variables qui changent ensuite (closure) — fais gaffe aux variables capturées.
Les multicast delegates qui retournent une valeur ne renvoient que le résultat du dernier handler — souvent une surprise pour les débutants.
Dans les endroits où on attend des expressions Expression<Func<int,bool>>, on ne peut pas substituer une statement-lambda — il faut exactement une expression-lambda.
GO TO FULL VERSION