1. Introduction
Imaginez que vous avez une machine à café. D'habitude elle fait juste du café et ne dérange personne, mais parfois le chef dit : « Tu peux, s'il te plaît, crier “Prêt !” après avoir fait le café ? » — et là il faut une configuration. On ne réécrit pas la machine à café. On crée simplement une fonction qu'elle appellera à la fin.
En C# ce rôle est joué par les délégués — ils permettent de passer dans les méthodes des bouts de code (méthodes, lambdas ou méthodes anonymes), afin que la méthode les invoque au bon moment. Grosso modo, un delegate est un type qui peut contenir des références vers des méthodes avec une certaine signature.
Définition d'un delegate
En C# un delegate se définit avec le mot-clé delegate. Exemple :
// Delegate qui référence des méthodes prenant un int et retournant bool
public delegate bool PredicateInt(int x);
Maintenant une variable de type PredicateInt pourra référencer n'importe quelle méthode (ou lambda !) qui prend un int et retourne un bool.
À quoi servent les delegates ?
- Passer la logique comme argument (par exemple pour trier, filtrer, gérer des événements)
- S'abonner à des événements (on en parlera plus tard)
- Implémenter des callbacks
- Des API flexibles où une partie du comportement est définie par l'appelant
Schéma visuel simple
| Type de delegate | Signature | Exemple d'appel |
|---|---|---|
|
|
|
|
|
|
|
|
|
2. Comment une lambda devient un delegate
Syntaxe
Quand vous écrivez une expression lambda, par exemple x => x > 5, vous créez en fait un objet delegate. Une lambda ne « vit » pas dans le vide : elle a forcément besoin d'un type (quelqu'un doit savoir quels sont ses paramètres et ce qu'elle retourne). Voilà pourquoi une expression lambda en C# est toujours convertie implicitement (ou explicitement) en delegate.
Exemple 1 : Lier un delegate à une méthode
// Définition explicite du delegate
public delegate bool MyPredicate(int number);
class Program
{
static void Main()
{
// On assigne une lambda à une variable de type MyPredicate
MyPredicate isEven = x => x % 2 == 0;
Console.WriteLine(isEven(4)); // true
Console.WriteLine(isEven(7)); // false
}
}
Exemple 2 : Utiliser les delegates standards
C# contient un ensemble de delegates génériques standards : Action, Func<>, Predicate<>. Ils sont utilisés quasiment partout où vous écrivez des lambdas dans le code.
// On utilise Func
Func
isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false
3. Delegates standards : Func, Action, Predicate
Func<...>
Utilisé pour les méthodes qui prennent quelque chose et retournent quelque chose.
Signature :
— Le dernier type est la valeur de retour, les autres avant sont les types des paramètres, par exemple :
Func<int, string> — prend un int, retourne un string
Func
intToString = number => "Nombre : " + number; Console.WriteLine(intToString(7)); // "Nombre : 7"
Action<...>
Utilisé quand il faut exécuter quelque chose sans rien retourner (void).
Action
printHello = name => Console.WriteLine("Salut, " + name + "!"); printHello("Vassili"); // "Salut, Vassili!"
Predicate<T>
Essentiellement un raccourci pour Func<T, bool>. Utilisé lorsque l'on a besoin d'une vérification logique sur un objet (true/false).
Predicate
isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true
Aide-mémoire visuel
| Delegate | Signature | Usage |
|---|---|---|
|
|
Transformation, projection |
|
|
Effets de bord, sortie |
|
|
Filtrage, recherche |
Quel type de delegate choisir pour une expression lambda ?
- Si une valeur de retour est attendue, prenez Func<...>
- Si la méthode ne retourne rien (void), utilisez Action<...>
- Si vous avez besoin d'une vérification conditionnelle, utilisez Predicate<T>
Exemple : Filtrer une liste
List
numbers = new List
{ 1, 2, 3, 4, 5, 6 }; // Attend Predicate
List
evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6
4. Nuances utiles
Expressions lambda et méthodes de collections : que se passe-t-il sous le capot ?
Quand vous appelez une méthode de collection en passant une lambda, par exemple :
var adults = users.Where(u => u.Age >= 18);
La méthode Where attend un argument de type Func<T, bool>. Autrement dit, votre lambda u => u.Age >= 18 est transformée par le compilateur en objet delegate de ce type.
Schéma : comment ça marche
Votre lambda --> Compilateur C# --> Objet delegate (Func
) (u => u.Age >= 18) [type connu] (Prêt à l'appel dans Where())
Plus en détail sur la typage : inference de type
Généralement le type du delegate est inféré automatiquement par le compilateur si le contexte est clair. Par exemple, pour la méthode List<T>.Find on attend un Predicate<T>, et le compilateur connaît le type du paramètre d'après la signature de la méthode.
List
words = new List
{ "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find attend Predicate
Console.WriteLine(result); // "three"
Si le contexte n'est pas clair, il faudra aider le compilateur :
// On précise explicitement le type
Func
check = x => x > 10;
Delegates retournés : usines de fonctions
Parfois des méthodes peuvent retourner des delegates — c'est-à-dire créer des "usines de fonctions". C'est pratique pour générer un comportement dynamique.
// Fonction qui retourne un delegate (lambda)
Func
GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15
Ça marche parce que la lambda (x => x * factor) capture la variable factor du contexte extérieur (closure/fermeture) et est retournée comme objet de type Func<int, int>.
5. Erreurs et malentendus avec delegates et lambdas
Signature incompatible
Le compilateur n'autorisera pas d'assigner une lambda à un delegate si les paramètres ou la valeur de retour ne correspondent pas.
Func
f = x => "Impossible de retourner une chaîne !"; // Erreur de compilation
Erreur en essayant d'utiliser une lambda sans delegate
On ne peut pas juste écrire une lambda et essayer de l'invoquer sans type :
// Ça ne fonctionnera pas - le compilateur ne peut pas inférer le type
// var myFunc = x => x * 2; // Erreur CS0815
// myFunc(10);
Pour que ça marche, il faut indiquer explicitement le type, ou fournir un contexte :
Func
myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20
Confusion entre Action, Func et Predicate
Parfois on peut choisir le mauvais type de delegate par erreur, ce qui provoque une incompatibilité de signature. Rappelez-vous la règle simple : Func — quand il y a un résultat, Action — quand il n'y a pas de résultat (void), Predicate<T> — quand on a besoin d'une réponse logique (bool).
GO TO FULL VERSION