Délégués, événements et expressions lambda
Les délégués
Un délégué est une sorte de pont entre l’appelant d’une méthode et la méthode voulue. Les délégués se distinguent entre les types et les instances. Un type délégué définit le protocole auquel l’appelant et la méthode appelée doivent se conformer. Cela inclut la liste des paramètres et le type de retour, en un mot la signature. Une instance de délégué est un objet qui fait référence à une ou plusieurs méthodes qui ont une signature conforme.
La déclaration d’un délégué est précédée du mot-clé delegate et la déclaration ne contient que la signature de la méthode comme pour un membre abstrait :
public delegate int Calcul(int i, int j);
Pour créer une instance de délégué, il suffit d’assigner une méthode dont la signature est conforme au délégué :
public class Classe1
{
public Classe1(int i, int j)
{
Calcul C = new Calcul(Addition);
int result = C.Invoke(i, j);
}
public int Addition(int i, int j)
{
return i + j;
}
}
Dans l’exemple précédent, la méthode Addition pourrait avoir des surcharges. Le compilateur prendrait alors automatiquement la bonne surcharge en fonction de la signature du délégué auquel la méthode est assignée.
L’instruction d’instanciation du délégué dans l’exemple précédent peut être abrégée :
Calcul C = new Calcul(Addition); // Notation complète
Calcul C = Addition; //...
Les événements
En travaillant avec les délégués, deux concepts sont mis en évidence : la diffusion et la souscription. Le diffuseur est un type relié à un délégué. C’est lui qui décide quand le délégué doit être invoqué. Le souscripteur est l’ensemble des méthodes attachées à un délégué. Un souscripteur est totalement indépendant des autres souscripteurs même au sein d’un délégué. Les événements formalisent ce schéma.
La manière la plus simple de déclarer un événement est d’ajouter le mot-clé event devant un membre délégué. L’événement Changed dans l’interface IReportChange a été créé précédemment de cette manière :
event EventHandler Changed;
Le type qui contient le diffuseur a un accès total à celui-ci. Cela peut être l’ajout, la suppression ou l’exécution de méthodes cibles. Les autres types ne pourront que souscrire à l’événement avec les opérateurs += et -=.
Analysez l’exemple suivant basé sur l’accesseur set de la propriété HasChanged de la classe ProjectSettings :
set
{
if (this.hasChanged != value)
{
this.hasChanged = value;
if...
Les expressions lambda
Une expression lambda est une méthode sans nom qui remplace une instance de délégué. Le compilateur transforme une expression lambda en un délégué.
La syntaxe d’une expression lambda est la suivante :
(paramètre1, paramètre2, ...) => expression ou instructions
Si l’expression ne contient qu’un seul paramètre, les parenthèses peuvent être omises comme dans l’exemple de la section suivante.
1. L’utilisation des expressions lambda
Prenons le délégué suivant :
public delegate int Multiplier(int i);
Il est possible d’assigner et d’invoquer une expression lambda comme ceci :
Multiplier M = x => x * 2;
int i = M(10); // i = 20
Le compilateur résout une expression lambda de ce type en créant une méthode privée et en déplaçant l’expression dans cette méthode.
Chacun des paramètres de l’expression lambda correspond à un paramètre du délégué et le type de l’expression correspond au type de retour du délégué. Dans l’exemple précédent, x correspond au paramètre i du délégué et l’expression x * 2 correspond au type de retour du délégué.
Il est possible d’abandonner un des paramètres de l’expression lambda en utilisant le caractère _ (underscore). Cela revient à avoir une fonction avec un paramètre optionnel :
List<Func<int, int, int>> multiplications =
new List<Func<int, int, int>>();
multiplications.Add((_, _) => 0 * 0);
multiplications.Add((int i, int _) => i * 0);
multiplications.Add((int i, int j) => i * j);
Une expression lambda peut contenir un bloc d’instructions au lieu d’une expression :
public delegate int Absolue(int i);
Absolue A = x =>
{
if (x < 0)
{
return -x;
}
else
{
return...