Les délégués en C#

Le delegué en C# nous rappelle les pointeurs de fonction en  C et C++ (il pointe toujours sur une fonction), mais nous offre plus de confort d'utilisation car il est de type Delegate, ou d'un de ses types dérivés. Par rapport aux pointeurs de fonctions en C et C++, le délégué est un objet, et nous apporte la sécurité par l'encapsulation.

Le delegué nous permet d'encapsuler une méthode (ou une série de méthodes). Si le délégué pointe sur une méthode statique, la méthode à appeler sera encapsulée. S'il pointe sur une méthode d'instance, une instance de l'objet dont provient la méthode est aussi encapsulée.

Comme le délégué est un objet de type Delegate, il ne dépend pas du type d'objet dont la fonction est pointée : nous pouvons donc l'utiliser sans connaître la classe de l'objet qu'il référence.

Nous utiliserons le plus souvent les délégués dans le cadre de la programmation événementielle car ils permettent une application aisée du pattern Observer (par exemple quand un composant graphique notifie à ses observateurs les modifications qu'il a subit).

Le délégué nous impose cependant une contrainte : les types d'arguments et la valeur de retour doivent être similaires à ceux de la fonction pointée.

Exemple de délégués

  1. using System;
  2. class DelegatePrg
  3. {
  4. public delegate int DelegateType(int num);
  5. static void Main(string[] args)
  6. {
  7. DelegateType myDelegate = new DelegateType(DelegateUtils.f1);
  8. int n1 = 0, n2 = 0;
  9. Console.WriteLine("Before tests : n1 = " + n1);
  10. n1 = myDelegate(n2);
  11. Console.WriteLine("After first test : n1 = " + n1);
  12. myDelegate = new DelegateType(DelegateUtils.f2);
  13. n1 = myDelegate(n2);
  14. Console.WriteLine("After second test : n1 = " + n1);
  15. myDelegate = new DelegateType(DelegateUtils.f1);
  16. myDelegate += new DelegateType(DelegateUtils.f2);
  17. n1 = myDelegate(n2);
  18. Console.WriteLine("After third test : n1 = " + n1);
  19. Console.WriteLine("Press any key to exit");
  20. Console.ReadKey();
  21. }
  22. }
  23. class DelegateUtils
  24. {
  25. public static int f1(int i)
  26. {
  27. Console.WriteLine("Before First Method : i = " + i);
  28. i++;
  29. Console.WriteLine("After First Method : i = " + i);
  30. return i;
  31. }
  32. public static int f2(int j)
  33. {
  34. Console.WriteLine("Before Second Method : j = " + j);
  35. j += 10;
  36. Console.WriteLine("After Second Method : j = " + j);
  37. return j;
  38. }
  39. }

Déclaration d'un délégué

Nous déclarons un type délégué par l'instruction suivante :

delegate int DelegateType(int num);

Dans cette instruction, nous déclarons que nous pouvons utiliser des objets de type DelegateType, et que les méthodes que nous encapsulerons dans ces objets délégués doivent renvoyer une valeur de type int, et doivent accepter une valeur int en argument.

Instanciation d'un délégué et affectation

DelegateType myDelegate = new DelegateType(DelegateUtils.f1);

Per cette instructions, nous déclarons que nous allons utiliser un objet de type DelegateType, que nous allons nommer myDelegate. Par la même occasion, nous créons l'objet en lui passant le nom de la méthode que nous désirons encapsuler.

Nous pouvons remarquer que nous passons au constructeur du délégué le nom de la méthode (dans le cas de notre exemple elle provient d'une autre classe nommée DelegateUtils), toujours sans parenthèses, ni aucun paramètre.

Lorsque nous utilisons notre délégué, c'est la méthode encapsulée que nous exécutons.
Dans notre exemple, lorsque nous demandons à afficher sur la console l'entier que nous retourne myDelegate, nous constatons qu'en réalité c'est la fonction f1 de la classe DelegateUtils qui est exécutée (myDelegate pointe sur f1).

Nous appelons le délégué comme nous appelons une méthode : en utilisant son nom suivi des arguments entre parenthèses.

Affectations de délégués

Nous pouvons réutiliser notre délégué en encapsulant une autre méthode, pourvu que cette dernière soit identique au niveau de la valeur de retour et des arguments.

myDelegate = new DelegateType(DelegateUtils.f2);

Lorsque nous demandons à afficher sur la console l'entier que nous retourne myDelegate, c'est à présent la fonction f2 qui est exécutée

Nous pouvons remarquer que nous devons à nouveau utiliser le constructeur du délégué pour lui passer le nom de la nouvelle fonction à encapsuler. En effet, nous ne pouvons modifier un délégué car il est immuable. Nous devons donc le recréer (c'est une nouvelle instanciation et non un changement d'affectation).

Nous pouvons cependant ajouter des fonctions au délégué (pour autant que les fonctions soient identiques au niveau de la valeur de retour et des arguments) gràce à une surcharge de l'opérateur "+=" pour obtenir un multicasting delegate. En réalité, comme dans le cas de la concaténation de chaînes de caractères, le délégué est immuable et un nouveau délégué est créé au moment de l'utilisation de l'opérateur "+".

myDelegate = new DelegateType(DelegateUtils.f1);
myDelegate += new DelegateType(DelegateUtils.f2);

Nous aurions aussi pu écrire ces instructions de cette manière :

DelegateType myDelegate, delegate1, delegate2;
delegate1 = new DelegateType(DelegateUtils.f1);
delegate2 = new DelegateType(DelegateUtils.f2);
myDelegate = delegate1 + delegate2;

Nous pouvons constater dans la console que chaque fonction est exécutée, car chaque fonction provoque un affichage sur la console.

Nous pouvons aussi remarquer que dans le cas de multicasting delegate chaque fonction est exécutée avec la valeur qui a été passée en argument au délégué. J'ai donc fait exprès dans cet exemple d'utiliser le même type de valeur de retour que celui passé en argument, pour bien démontrer ce phénomène et éviter toute confusion par la suite.

Affichage

Pour ceux qui n'ont pas la patience de faire des essais, voici l'affichage console produit par ce programme :

Before tests : n1 = 0
Before First Method : i = 0
After First Method : i = 1
After first test : n1 = 1
Before Second Method : j = 0
After Second Method : j = 10
After Second test : n1 = 10
Before First Method : i = 0
After First Method : i = 1
Before Second Method : j = 0
After Second Method : j = 10
After Third test : n1 = 10
Press any key to exit
_

Version en cache

21/11/2024 09:40:56 Cette version de la page est en cache (à la date du 21/11/2024 09:40:56) afin d'accélérer le traitement. Vous pouvez activer le mode utilisateur dans le menu en haut pour afficher la dernère version de la page.

Document créé le 05/10/2006, dernière modification le 07/04/2023
Source du document imprimé : https://www.gaudry.be/csharp-delegate.html

L'infobrol est un site personnel dont le contenu n'engage que moi. Le texte est mis à disposition sous licence CreativeCommons(BY-NC-SA). Plus d'info sur les conditions d'utilisation et sur l'auteur.