1. Introduction
Quand vous programmez, parfois il faut décrire une petite action qui ne sert qu'une seule fois et n'a pas d'utilité ailleurs. Créer une méthode séparée pour ça, c'est comme enfoncer un clou avec le marteau d'un kit de montage d'avion. C'est beaucoup plus simple de prendre quelque chose de proche et de résoudre le problème «sur place».
Méthode anonyme — c'est un bout de code temporaire qui n'a pas besoin d'un nom parce qu'il est utilisé exactement là où il est créé. Il :
- Est déclaré à l'intérieur d'une autre méthode.
- N'a pas de nom.
- Est généralement utilisé une seule fois — par exemple passé à un delegate ou abonné à un event.
Aujourd'hui on utilise plus souvent les expressions lambda (l'opérateur =>), mais comprendre les méthodes anonymes aide à saisir comment fonctionnent les delegates et le modèle d'événements en C#.
Quand une méthode anonyme est pratique ?
- Il faut rapidement passer un bout de logique en paramètre, par exemple pour trier, filtrer, gérer des events, etc.
- Ce n'est pas la peine d'alourdir la classe avec des méthodes séparées pour une logique ponctuelle.
- Quand on veut que le code soit compact et lisible : toute la «petite logique métier» est au même endroit.
Un peu d'histoire
La possibilité de créer des méthodes anonymes est apparue pour la première fois en C# dans la version 2.0. Avant ça, utiliser des delegates était verbeux : il fallait déclarer une méthode nommée même si la logique n'était utilisée qu'à un seul endroit. Avec l'arrivée des méthodes anonymes tout s'est simplifié, et avec les expressions lambda l'idée a gagné en concision.
2. Classique : delegates et méthode «nommée» habituelle
Avant de voir les méthodes anonymes, rappelons la façon standard de transmettre du comportement via des delegates. Imaginons une appli «Base de livres» où on veut filtrer des livres selon différentes conditions.
// On définit le delegate
public delegate bool BookFilter(Book book);
// Classe Book
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
Avant, on écrivait comme ça :
// Fonction filtre séparée
public static bool IsClassic(Book b)
{
return b.Year < 1970;
}
// Quelque part dans le code d'utilisation
BookFilter filter = IsClassic;
Déclarer à chaque fois une méthode séparée n'est pas toujours pratique. Et si on a des dizaines de filtres ?
3. Méthode anonyme : minimalisme, compatible avec les delegates
La méthode anonyme permet de définir le filtre directement là où il est nécessaire :
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
Et voilà ! Pas de méthodes inutiles, tout est sur place. delegate agit comme la déclaration d'une fonction sans nom.
Syntaxe générale
delegate([arguments])
{
// corps de la méthode
};
Exemple intégré dans un programme :
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
public delegate bool BookFilter(Book book);
class Program
{
static void Main()
{
Book[] books = {
new Book { Title = "Le Maître et Marguerite", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
// Filtre anonyme pour les classiques
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
foreach (Book book in books)
{
if (filter(book)) // on appelle la fonction anonyme !
Console.WriteLine($"{book.Title} — un classique !");
}
}
}
Sortie :
Le Maître et Marguerite — un classique !
4. Méthode anonyme avec différents delegates
Les méthodes anonymes fonctionnent bien avec tous les delegates. Par exemple, les Action et Func<T, TResult> standards, que nous détaillerons plus loin.
Action<string> sayHello = delegate(string name)
{
Console.WriteLine($"Salut, {name}!");
};
sayHello("Monde"); // Salut, Monde!
Petite illustration : comment marche une méthode anonyme «jetable»
La façon la plus simple de voir une méthode anonyme, c'est comme un travailleur temporaire : il arrive pour une tâche courte, exécute — et disparaît. On lie le travailleur et la tâche via un delegate.
Func<int, int, int> sum = delegate (int a, int b) {
return a + b;
};
Console.WriteLine(sum(5, 7)); // 12
Usage : tri, recherche, traitement de collections
Supposons qu'on ait une liste de livres qu'on veut trier par année de publication. Déclarer une méthode séparée juste pour une comparaison ponctuelle est excessif.
var books = new List<Book>
{
new Book { Title = "Le Maître et Marguerite", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
books.Sort(delegate(Book a, Book b)
{
return a.Year.CompareTo(b.Year);
});
foreach (var book in books)
Console.WriteLine($"{book.Title} ({book.Year})");
5. Astuces utiles
La méthode anonyme, est-ce la même chose que l'expression lambda ?
En C# moderne on utilise plus souvent les expressions lambda (=>), et en général il y a peu de différences, mais il y a des distinctions importantes :
- Les expressions lambda sont plus courtes et plus expressives.
- Les lambdas ont des règles de capture de variables univoques (on apprendra ça juste après).
- Les méthodes anonymes sont apparues plus tôt et se trouvent parfois dans du code «hérité» de vieux projets.
Voici à quoi ressemblerait notre exemple avec une lambda :
BookFilter filter = b => b.Year < 1970;
Cela dit, connaître l'ancien et le nouveau syntaxe est utile pour les entretiens, lire du code d'autres personnes et comprendre en profondeur les delegates.
Méthode anonyme avec ou sans paramètres
Si le delegate ne prend rien, on peut l'écrire sur une seule ligne :
Action printHello = delegate { Console.WriteLine("Salut!"); };
printHello(); // Salut!
S'il prend des paramètres — on peut quand même tout mettre sur une ligne :
Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36
Méthode anonyme et expression lambda
| Méthode anonyme | Expression lambda | |
|---|---|---|
| Syntaxe | |
|
| Capture de variables | Oui | Oui |
| Popularité | Peu utilisé | Standard |
| Retour de valeur | Peut/obligatoire | Peut/obligatoire |
| Multi-lignes | Possible | Possible |
6. Méthodes anonymes — détails et subtilités
Capture des variables locales
Les méthodes anonymes peuvent utiliser des variables de la méthode englobante — ce mécanisme s'appelle une «closure». Par exemple :
int minYear = 1970;
BookFilter filter = delegate(Book book)
{
return book.Year < minYear;
};
Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True
Si on change ensuite la variable minYear, le filtre utilisera la nouvelle valeur !
On peut omettre les paramètres
Si aucun paramètre n'est nécessaire :
Action sayHi = delegate { Console.WriteLine("Salut!"); };
Passage de null
Si aucun method n'est assignée au delegate (par exemple pas de méthode anonyme), sa valeur est null, et tenter de l'appeler provoquera une NullReferenceException. Faites attention.
Multi-lignes
Une méthode anonyme peut contenir un bloc de code complet, des conditions, des boucles et même d'autres appels :
Action manyThings = delegate
{
Console.WriteLine("C'est parti !");
for (int i = 0; i < 3; i++)
Console.WriteLine(i);
Console.WriteLine("C'est fini !");
};
manyThings();
7. Erreurs typiques et particularités
Parfois les développeurs se trompent sur la portée des variables : capturer des valeurs d'une boucle ou d'une méthode imbriquée peut donner des résultats inattendus.
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — surprise !
Le problème est que la méthode anonyme «voit» la variable i — quand la boucle est finie, i vaut 3. Toutes les méthodes affichent la même valeur. Pour obtenir le comportement voulu, mieux vaut capturer la variable ainsi :
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(delegate { Console.WriteLine(current); });
}
GO TO FULL VERSION