CodeGym /Cours /C# SELF /Combiner les expressions lambda et les délégués

Combiner les expressions lambda et les délégués

C# SELF
Niveau 50 , Leçon 4
Disponible

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
Func<int, int>
int
Func<int, int> f = x => x + 1;
Action<string>
void
Action<string> a = s => ...;
Predicate<double>
bool
Predicate<double> p = d => ...;

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.

1
Étude/Quiz
Les délégués - un type pour les expressions lambda, niveau 50, leçon 4
Indisponible
Les délégués - un type pour les expressions lambda
Lien entre les expressions lambda et les délégués
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION