Introduction aux Delegates et les évènements en .Net Partie1

Suite à la demande de mes collègues qui préparent la certification .Net, j’écris ce post pour éclaircir la notion de délégation dans C#.

dotnet_logo

 

Définition et intérêt de type delegate

Un type délégué en C#  est un concept très important dans le développement d’applications .NET, c’est un type qui permet de référencer une méthode d’une classe.

Grâce aux delegates vous pouvez transmettre une méthode comme paramètre d’une autre, c’est l’équivalent d’un pointeur de fonction en c/c++.

On considère l’instruction suivante :

public delegate int Opération(int n1, int n2)

Elle définit  un type appelé Opération qui représente un prototype de fonction acceptant deux entiers et rendant un entier. C'est le mot clé

delegate qui fait d’Opération une définition de prototype de fonction.

Une variable op de type Opération aura pour rôle d'enregistrer une liste de fonctions correspondant au prototype Opération :

int f1(int,int)

int f2(int,int)

...

int fn(int,int)

  • L'enregistrement d'une méthode fi dans la variable op se fait par op=new Opération(fi) ou plus simplement par op=fi.
  • Pour ajouter une méthode fj à la liste des fonctions déjà enregistrées, on écrit op+= fj. Pour enlever une méthode fk déjà enregistrée on écrit op-=fk.
  •  Si dans notre exemple on écrit n=op(n1,n2), l'ensemble des méthodes enregistrées dans la variable op seront exécutées avec les paramètres n1 et n2.
  • Le résultat n récupéré sera celui de la dernière méthode exécutée.

Exemple

Il n'y a pas mieux qu'un exemple pour comprendre les delegates, le voici :

1.using System;

2. namespace EXP {

3. class Program1 {

4.             // définition d'un prototype de fonction

5.            // accepte 2 entiers en paramètre et rend un entier

6.           public delegate int Opération(int n1, int n2);

7.

8.          // deux méthodes d'instance correspondant au prototype

9.          public static int Ajouter(int n1, int n2) {

10.           Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");

11.              return n1 + n2;

12.           }//ajouter

13.

14.       public static int Soustraire(int n1, int n2) {

15.         Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");

16.         return n1 - n2;

17.       }//soustraire

18.

19.      // Exécution d'un délégué

20.      public static int Execute(Opération op, int n1, int n2){

21.         return op(n1, n2);

22.       }

23.

24.      static void Main(string[] args) {

25.         // exécution du délégué Ajouter

26.         Console.WriteLine(Execute(Ajouter, 2, 3));

27.        // exécution du délégué Soustraire

28.        Console.WriteLine(Execute(Soustraire, 2, 3));

29.       // exécution d'un délégué multicast

30.       Opération op = Ajouter;

31.       op += Soustraire;

32.       Console.WriteLine(Execute(op, 2, 3));

33.       // on retire une fonction du délégué

34.       op -= Soustraire;

35.      // on exécute le délégué

36.      Console.WriteLine(Execute(op, 2, 3));

37.     // l’instruction suivante est aussi acceptée

38.     Console.WriteLine(Execute(Soustraire, 2, 3));

39.    }

40.  }

40. }

Ce qui donne comme résultat :

image

Ligne 20, la méthode Execute attend une référence sur un objet du type delegate Opération  (ligne 6). Cela permet d’y passer différentes méthodes (lignes 26, 28, 32 et 36).

La variable op est de type delagate Opération, il contiendra une liste de méthodes ayant la signature du type delegate Opération.

Lorsque op contient deux méthodes ajouter et soustraire, l’appel à op(2,3)  entraîne l’exécution des deux méthodes.

La ligne 38 montre qu’on peut directement passer le nom de la  méthode de même signature comme paramètre à la méthode exécute

Interface ou delegate

La propriété de polymorphisme vue dans la méthodes execute ,  peut être obtenue avec l’interface suivante :

1. using System;

2.

3. namespace EXP {

4.

5. // interface IOperation

6. public interface IOperation {

7. int operation(int n1, int n2);

8. }

On peut implémenter la méthode de cet interface avec tout ce qu’on veut. C’est quoi la différence alors ?

Et bien quand on utilise une interface, on est obligé de créer une classe qui l’implémente,   en effet, pour implémenter la méthode opération avec addition, il faut créer une classe AdditionClass qui implémente l’interface IOperation et qui met le corps addition dans  la méthode opération.

Si on veut utiliser la soustraction, alors il faut créer une autre classe SoustractionClasss qui implémente l’interface et qui met la soustraction dans le corps  la méthode opération.

Avec Delegate ce transfert d’une méthode à l’autre est trivial, une simple inscription  peut faire l’affaire.

Lambda Expression

A partir du frameWork .Net3.0 , une simplification de la syntaxe de delegate est apparue. C’est ce qu’on appelle les « lambda expression », sa syntaxe est comme suit :

(paramètres séparés par des virgules) => { corps de la méthode}.

Dans l’exemple précédent on remarque 3 étapes pour créer un delegate :

  • Etape 1 : déclaration du type Delegate traduit dans l’exemple par : public delegate int Opération(int n1, int n2);  ligne 6
  • Etape 2 : création des fonctions qui seront inscrites dans le delegate : comme dans exemple la fonction Ajouter  et Soustraire
  • Etape 3 : passer les noms de ces fonctions comme paramètres: par exemple  execute(Soustraire,2,3) ligne 38.

Avec le lambda Expression on peut se passer de l’étape 2, donc si on veut passer la soustraction à la méthode execute on fait l’appel suivant :

Execute( (a,b)=>{ return a-b},2,3)

On peut simplifier encore en enlevant la première étape qui est la déclaration de delagate,  comment ça ?  On utilise une chose qui n’est pas déclarer ? Et oui avec le .NET3.5 , deux nouvelles notions sont apparues :

  • Type « Func »

C’est un type delegate qui a une valeur de retour et qui prend en maximum 4 paramètres, la valeur de retour est le dernier paramètre

Func<TResult>

Func<T1,TResult>

Func<T1,T2,TResult>

Func<T1,T2,T3,TResult>

Func<T1,T2,T3,T4TResult>

N’importe quelle fonction qui retourne une valeur et qui a au maximum 4 paramètres peut être représentée par ce delegate.

  • Type « Action »

C’est la même chose que Func sauf qu’elle ne retourne pas de valeur.

Action<T>

Action<T1,T2>

Action<T1,T2,T3>

Action<T1,T2,T3,T4>

Toutes les méthodes void qui ont au maximum 4 paramètres peuvent être représentées par ce delegate.

Donc notre méthode Execute devient comme suit, sans déclarer auparavant un delegate:

public static int Execute(Func<int,int,int> op, int n1, int n2){

return op(n1, n2)

}

Conclusion

Le delegate est une  notion très utilisée dans le DotNet, on la trouve dans Linq , dans les événements ces derniers seront présenter dans un blog dédié.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.