CodeGym /Cours /C# SELF /Expressions lambda avec Fu...

Expressions lambda avec Func, Action, Predicate

C# SELF
Niveau 50 , Leçon 1
Disponible

1. Introduction

Vous savez ce qui est intéressant ? L'expression lambda en elle-même — c'est comme une recette sans casserole. Elle décrit quoi faire, mais elle a besoin d'un "contenant" pour vivre dans le code. En C# il y a des types delegates génériques prêts à l'emploi : Func, Action et Predicate.

Imaginez-les comme des moules prêts pour vos lambdas — prenez celui qu'il faut et versez votre logique dedans. Pas besoin de réinventer la roue avec des delegates personnalisés quand tout est déjà là.

Un peu d'histoire

Au début, quand il fallait passer une fonction en paramètre, il fallait déclarer son propre type delegate. C'était long, pénible et ça ressemblait à ça :

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

Quand Func, Action et Predicate sont apparus en C#, on a pu oublier la déclaration manuelle de delegates dans 90% des cas. Maintenant c'est beaucoup plus simple et générique :

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — fonction avec retour

Syntaxe et but

Func — c'est un delegate générique qui prend de 0 à 16 paramètres et qui retourne une valeur.

Func<int, int, int> sum = (x, y) => x + y;

Func<type1, type2, ..., typeN, TResult> — tous les paramètres sauf le dernier sont les arguments, le dernier est le type de retour.

Exemples

1. Somme de deux nombres

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. Élever un nombre au carré

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. Sans paramètres

Func<string> greet = () => "Hello, lambda!";
Console.WriteLine(greet());

Schéma visuel

Signature Exemple Description
Func<int, int>
x => x * 2
Prend un int, retourne un int
Func<int, int, int>
(a, b) => a + b
Deux int, retourne un int
Func<string>
() => "hi"
Ne prend rien, retourne une chaîne

Comment ça ressemble dans votre appli

Supposons que dans notre mini-appli (le "Répertoire d'utilisateurs" fictif), on a une liste de nombres — on veut leur appliquer différentes transformations. Par exemple, élever au carré ou additionner avec un nombre fixe :

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — action sans retour

Action — delegate générique pour des méthodes qui font quelque chose (par ex. affichent), mais ne retournent rien.

Peut accepter de 0 à 16 paramètres, mais retourne toujours void.

Exemples

1. Afficher à l'écran

Action<string> print = text => Console.WriteLine("Data: " + text);
print("Hello, mir!");

2. Action sans paramètres

Action greet = () => Console.WriteLine("Welcome!");
greet();

3. Action avec plusieurs paramètres

Action<int, int> showSum = (a, b) => Console.WriteLine($"Sum: {a + b}");
showSum(2, 3); // Sum: 5

Schéma visuel

Signature Exemple Description
Action
() => ...
Sans paramètres, sans retour
Action<int>
x => ...
Un paramètre
Action<int, string>
(x, s) => ...
Plusieurs paramètres

Dans notre application

Ajoutons dans le répertoire un méthode pour afficher tous les noms :

List<string> names = new() { "Anna", "Boris", "Vika" };
Action<string> printName = name => Console.WriteLine("User: " + name);

names.ForEach(printName);
// ou ainsi : names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — oui ou non ?

Quand il faut une fonction qui renvoie seulement true ou false pour un paramètre, utilisez Predicate<T>. C'est juste un delegate qui prend un paramètre et retourne un bool.

Predicate — l'enveloppe officielle pour "on veut un test booléen" pour une lambda.

Exemples

1. Vérifier si un nombre est supérieur à 5

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. Utilisation avec la méthode List<T>.Find

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // utilise Predicate<int>
Console.WriteLine(found); // 7

3. Tous sont adultes ?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll prend un Predicate<int>

Quelle est la différence avec Func<T, bool> ?

En pratique, ils sont interchangeables. Même la doc Microsoft dit : "Predicate<T> est juste Func<T, bool> pour des API spécifiques". Mais certaines méthodes de la bibliothèque standard demandent précisément un Predicate.

5. Comment les lambdas "rentrent" dans Func, Action, Predicate

Quand vous écrivez une lambda, C# analyse : "Ah, sa forme correspond au delegate attendu — on peut l'assigner !"

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

Partout des lambdas ! Mais sous le capot — trois delegates différents avec des signatures différentes.

Application sur un code "réel"

List<User> users = new() {
    new User("Anna", 24),
    new User("Boris", 17),
    new User("Vika", 31),
};

// Fonction qui retourne seulement les utilisateurs majeurs (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("Adults list: " + string.Join(", ", adults.Select(u => u.Name)));

Et si on veut afficher les noms de tous les utilisateurs via Action<User> :

users.ForEach(user => Console.WriteLine(user.Name));

Les obtenir comme noms (Func<User, string>) :

IEnumerable<string> names = users.Select(user => user.Name);

Tableau pour la clarté

Delegate Signature Exemple de lambda Où utilisé
Func<T, U>
T → U
user => user.Name
Select, toutes les transformations
Action<T>
T → void
user => Console.WriteLine(...)
ForEach, méthodes-actions
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, filtres

6. Exemples dans l'application : pas à pas

Élargissons notre petite appli "Répertoire d'utilisateurs". Supposons qu'on a la classe User :

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — vérifier si l'utilisateur est majeur

Func<User, bool> isAdult = user => user.Age >= 18;

On l'utilise dans LINQ :

var adults = users.Where(isAdult);

2. Predicate<User> — chercher un utilisateur actif

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — désactiver un utilisateur

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — obtenir une description courte

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

Toutes ces lambdas sont du "code sous forme de données" que l'on peut passer aux méthodes, stocker dans des variables, combiner.

7. Nuances non évidentes et erreurs typiques

1. La lambda doit correspondre à la signature du delegate. Si la signature ne correspond pas, vous aurez une erreur de compilation.

Func<int, string> wrong = x => x * 2; // erreur : on attend string, on obtient int
// Correct :
Func<int, string> right = x => (x * 2).ToString();

2. Ne pas oublier void vs return. Action ne retourne rien — essayer d'écrire quelque chose comme Action<int> a = x => x * x; ne fonctionnera pas, parce qu'on retourne une valeur alors qu'on ne doit pas.

3. Predicate<T> et Func<T, bool> sont souvent interchangeables, mais pas toujours. Parfois les méthodes de collection attendent précisément un Predicate<T>, parfois un Func<T, bool>. L'assignation directe ne fonctionne pas sans encapsulation explicite.

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // erreur
// Mais :
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // on peut convertir via le constructeur
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION