CodeGym /Cours /C# SELF /Délégués comme type pour les expressions lambda

Délégués comme type pour les expressions lambda

C# SELF
Niveau 50 , Leçon 0
Disponible

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
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

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
Func<T1, TResult>
Result(T1)
Transformation, projection
Action<T1>
void(T1)
Effets de bord, sortie
Predicate<T>
bool(T)
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).

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION