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 |
|---|---|---|
|
|
Prend un int, retourne un int |
|
|
Deux int, retourne un int |
|
|
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 |
|---|---|---|
|
|
Sans paramètres, sans retour |
|
|
Un paramètre |
|
|
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é |
|---|---|---|---|
|
T → U | |
Select, toutes les transformations |
|
T → void | |
ForEach, méthodes-actions |
|
T → bool | |
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
GO TO FULL VERSION