1.1 Définition classe de délégation - délégué
1.2 Délégué et méthodes de classe - définition
1.3 Délégué et méthodes de classe - informations pendant l'exécution
1.4 Délégué et méthodes d'instance - définition
1.5 Plusieurs méthodes pour le même délégué
1.6 Exécution des méthodes d'un délégué multicast - Exemple de code
Il existe en Delphi la notion de pointeur de méthode utilisée pour implanter la notion de gestionnaire d"événements. C# a repris cette idée en l'encapsulant dans un concept objet plus abstrait : la notion de classes de délégations. Dans la machine virtuelle CLR, la gestion des événements est fondée sur les classes de délégations, il est donc essentiel de comprendre le modèle du délégué pour comprendre le fonctionnement des événements en C#.
1. Les classes de délégations
Note de microsoft à l'attention des développeurs :
La classe Delegate est la classe de base pour les types délégué. Toutefois, seuls le système et les compilateurs peuvent dériver de manière explicite de la classe Delegate ou MulticastDelegate. En outre, il n'est pas possible de dériver un nouveau type d'un type délégué. La classe Delegate n'est pas considérée comme un type délégué. Il s'agit d'une classe utilisée pour dériver des types délégué.
1.1 Définition classe de délégation - délégué
Le langage C# contient un mot clé delegate , permettant au compilateur de construire une classe dérivée de la classe MulticastDelegate dérivée elle-même de la classe Delegate. Nous ne pouvons pas instancier une objet de classe Delegate, car le constructeur est spécifié protected et n'est donc pas accessible :
![]()
C'est en fait via ce mot clé delegate que nous allons construire des classes qui ont pour nom : classes de délégations. Ces classes sont des classes du genre référence, elles sont instanciables et un objet de classe délégation est appelé un délégué. Un objet de classe délégation permet de référencer (pointer vers) une ou plusieurs méthodes. Il s'agit donc de l'extension de la notion de pointeur de méthode de Delphi. Selon les auteurs une classe de délégation peut être ausi nommée classe déléguée, type délégué voir même tout simplement délégué. Il est essentiel dans le texte lu de bien distinguer la classe et l'objet instancié.
Lorsque nous utiliserons le vocable classe délégué ou type délégué nous parlerons parlerons de la classe de délégation d’un objet délégué.
![]()
Un objet de classe délégation permet de référencer (pointer vers) une ou plusieurs méthodes.
Il donc possible de créer un nouveau type de délégué (une nouvelle classe) dans un programme, ceci d'une seule manière : en utilisant le qualificateur delegate. Ci-dessous nous déclarons un type délégué (une nouvelle classe particulière) nommé NomTypeDelegue :
delegate string NomTypeDelegue ( int parametre) ;
Un objet délégué peut donc être instancé à partir de cette "classe" comme n'importe quel autre objet :
NomTypeDelegue Obj = new NomTypeDelegue ( <paramètre> )
Il ne doit y avoir qu'un seul paramètre <paramètre> et c'est obligatoirement un nom de méthode. Soit MethodeXYZ le nom de la méthode passé en paramètre, nous dirons alors que le délégué (l'objet délégué) référence la méthode MethodeXYZ .
![]()
Les méthodes référencées par un délégué peuvent être : des méthodes de classe ( static )Toutes les méthodes référencées par un même délégué ont la même signature partielle :
ou
des méthodes d'instance
- même type de retour du résultat,
- même nombre de paramètres,
- même ordre et type des paramètres,
- seul leur nom diffère.
1.2 Délégué et méthodes de classe - définition
Un type commençant par le mot clef delegate est une classe délégation.
ci-dessous la syntaxe de 2 exemples de déclaration de classe délégation :
Un objet instancié à partir de la classe Deleguer1 est appelé un délégué de classe Deleguer1 :
- delegate string Deleguer1( int x ) ;
- delegate void Deleguer2( string s ) ;
- Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ;
Un objet instancié à partir de la classe Deleguer2 est appelé un délégué de classe Deleguer2 :
- où Fonc1 est une méthode :
- static string Fonc1 ( int x ) { ... }
- Deleguer2 FoncDeleg2 = new Deleguer2 ( Fonc2 ) ;
- où Fonc2 est une autre méthode :
- static void Fonc2 ( string x ) { ... }
Nous avons créé deux types délégations nommés Deleguer1 et Deleguer2 :
- Le type Deleguer1 permet de référencer des méthodes ayant un paramètre de type int et renvoyant un string.
- Le type Deleguer2 permet de référencer des méthodes ayant un paramètre de type string et ne renvoyant rien.
Les fonctions de classe Fonc1 et Fonc11 répondent à la signature partielle du type Deleguer1
On peut créer un objet (un délégué) qui va référencer l'une ou l'autre de ces deux fonctions :
- static string Fonc1 ( int x ) { ... }
- static string Fonc11 ( int x ) { ... }
ou bien
- Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ;
On peut maintenant appeler le délégué FoncDeleg1 dans une instruction avec un paramètre d'entrée de type int, selon que le délégué référence Fonc1 ou bien Fonc11 c'est l'une ou l'autre des ces fonctions qui est en fait appelée.
- Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc11 ) ;
Source d'un exemple C# et exécution :
delegate string Deleguer1 ( int x );
class ClasseA {
static string Fonc1 ( int x ) {
return ( x * 10 ) .ToString ( );
}
static string Fonc11 ( int x ) {
return ( x * 100 ) .ToString ( );
}
static void Main ( string [] args ) {
string s = Fonc1 ( 32 );
System .Console.WriteLine ("Fonc1(32) = " + s );
s = Fonc11 ( 32 ); // appel de fonction classique
System .Console.WriteLine ("Fonc11(32) = " + s );
System .Console.WriteLine ("\nLe délégué référence Fonc1 :");
Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ;
s = FoncDeleg1 ( 32 ); // appel au délégué qui appelle la fonction
System .Console.WriteLine ("FoncDeleg1(32) = " + s );
System .Console.WriteLine ("\nLe délégué référence maintenant Fonc11 :");
FoncDeleg1 = new Deleguer1 ( Fonc11 ) ; // on change d'objet référencé (de fonction)
s = FoncDeleg1 ( 32 ); // appel au délégué qui appelle la fonction
System .Console.WriteLine ("FoncDeleg1(32) = " + s );
System .Console.ReadLine ( );
}
}
Résultats d'exécution sur la console :
![]()
1.3 Délégué et méthodes de classe - informations pendant l'exécution
Comme une référence de délégué peut pointer (référencer) vers des méthodes de classes différentes au cours de l'exécution, il est intéressant d'obtenir des informations sur la méthode de classe actuellement référencée par le délégué.
Nous donnons ci-dessous les deux propriétés publiques qui sont utiles lors de cette recherche d'informations, elles proviennent de la classe mère Delegate non héritable par programme :
![]()
![]()
La propriété Target sert plus particulièrement lorsque le délégué référence une méthode d'instance, la propriété Method n'est utilisable que lorsque la méthode référencée par le délégué est une méthode de classe (méthode marquée static). Lorsque la méthode référencée par le délégué est une méthode de classe le champ Target a la valeur null.
Ci-dessous nous avons extrait quelques informations concernant la propriété Method qui est elle-même de classe MethodInfo :
![]()
Ces propriétés sont des membres de la propriété Method qui est applicable uniquement lorsque le délégué en cours référence une méthode de classe (qualifiée static). Nous illustrons dans la figure ci-après, dans le cas d'une méthode de classe, l'utilisation des propriétés Name, DeclaringType et ReturnType membres de la propriété Method :
![]()
Nous obtenons ainsi des informations sur le nom, le type du résultat et la classe de la méthode static pointée par le délégué.
Source complet exécutable d'un exemple d'information sur la méthode de classe référencée :
namespace PrDelegate {
delegate string Deleguer1 ( int x );
class ClasseA {
static string Fonc1 ( int x ) {
return ( x * 10 ) .ToString ( );
}
static void Main ( string [] args ) {
System .Console.WriteLine ("\nLe délégué référence Fonc1 :");
Deleguer1 FoncDeleg1 = new Deleguer1 ( Fonc1 ) ;
System .Console.WriteLine ( "nom : "+FoncDeleg1.Method.Name );
System .Console.WriteLine ("classe : "+ FoncDeleg1.Method.DeclaringType.ToString( ) );
System .Console.WriteLine ( "retour : "+FoncDeleg1.Method.ReturnType.ToString( ) );
System .Console.ReadLine ( );
}
}
}
Résultats d'exécution sur la console :
![]()
1.4 Délégué et méthodes d'instance - définition
Outre un méthode de classe, un délégué peut pointer aussi vers une méthode d'instance(une méthode d'un objet). Le fonctionnement (déclaration, instanciation, utilisation) est identique à celui du référencement d'une méthode de classe, avec syntaxiquement l'obligation, lors de l'instanciation du délégué, d'indiquer le nom de l'objet ainsi que le nom de la méthode Obj.Methode (similitude avec le pointeur de méthode en Delphi).
Ci-dessous la syntaxe d'un exemple de déclaration de classe délégation pour une méthode d'instance, nous devons :
1°) Déclarer ne classe contenant une méthode public
class clA {
public int meth1(char x) {
....
}
}
2°) Déclarer un type délégation
delegate int Deleguer( char x ) ;
3°) Instancier un objet de la classe clA
clA ObjA = new clA ( ) ;4°) Instancier à partir de la classe Deleguer un délégué
Deleguer FoncDeleg = new Deleguer ( ObjA.meth1 ) ;
![]()
Nous illustrons dans la figure ci-après, dans le cas d'une méthode d'instance, l'utilisation de membres de la propriété Method et de la propriété Target :
![]()
Source complet exécutable d'un exemple d'information sur la méthode d'instance référencée :
namespace PrDelegate {
delegate int Deleguer ( char x );
class ClasseA {
public int meth1 ( char x ) {
return x ;
}
static void Main ( string [] args ) {
ClasseA ObjX , ObjA = new ClasseA( );
System .Console.WriteLine ("Un délégué référence ObjA.meth1 :");
Deleguer FoncDeleg = new Deleguer ( ObjA.meth1 ) ;
ObjX = (ClasseA)FoncDeleg.Target;
if (ObjX.Equals(ObjA))
System.Console.WriteLine ("Target référence bien ObjA");
else System.Console.WriteLine ("Target ne référence pas ObjA");
System.Console.WriteLine ( "\nnom : "+FoncDeleg.Method.Name );
System.Console.WriteLine ("classe : "+ FoncDeleg.Method.DeclaringType.ToString( ) );
System.Console.WriteLine ( "retour : "+FoncDeleg.Method.ReturnType.ToString( ) );
System.Console.ReadLine ( );
}
}
}
Résultats d'exécution sur la console :
![]()
Dans le programme précédent, les lignes de code suivantes :
ObjX = (ClasseA)FoncDeleg.Target ;
if (ObjX.Equals(ObjA))
System.Console.WriteLine ("Target référence bien ObjA") ;
else System.Console.WriteLine ("Target ne référence pas ObjA") ;
servent à faire "pointer" la référence ObjX vers l'objet vers lequel pointe FoncDeleg.Target. La référence de cet objet est transtypée car ObjX est de type ClasseA, FoncDeleg.Target est de type Object et le compilateur n'accepterait pas l'affectation ObjX = FoncDeleg.Target. Le test if (ObjX.Equals(ObjA))... permet de nous assurer que les deux références ObjX et ObjA pointent bien vers le même objet.
1.5 Plusieurs méthodes pour le même délégué
C# autorise le référencement de plusieurs méthodes par le même délégué, nous utiliserons le vocabulaire de délégué multicast pour bien préciser qu'il référence plusieurs méthodes. Le délégué multicast conserve les référencements dans une liste d'objet. Les méthodes ainsi référencées peuvent chacune être du genre méthode de classe ou méthode d'instance, elles doivent avoir la même signature.
Rappelons qu'un type délégué multicast est une classe qui hérite intrinsèquement de la classe MulticasteDelegate :
![]()
La documentation de .Net Framework indique que la classe MulticasteDelegate contient en particulier trois champs privés :
Object _target ;
Int32 _methodPtr ;
MulticasteDelegate _prev ;
Le champ _prev est utilisé pour maintenir une liste de MulticasteDelegate
Lorsque nous déclarons un progarmme comme celui-ci :
delegate int Deleguer ( char x );
class ClasseA {
public int meth100 ( char x ) {
System.Console.WriteLine ("Exécution de meth100('"+x+"')");
return x+100 ;
}
static void Main ( string [] args ) {
ClasseA ObjA = new ClasseA( );
Deleguer FoncDeleg = new Deleguer ( ObjA.meth100 ) ;
}
}
Lors de l'exécution, nous avons vu qu'il y a création d'un ObjA de ClasseA et d'un objet délégué FoncDeleg, les propriétés Method et Target sont automatiquement initialisées par le compilateur :
![]()
En fait, ce sont les champs privés qui sont initialisés et les propriétés Method et Target qui sont en lecture seuelement, lisent les contenus respectifs de _methodPtr et de _target, le champ _prev étant pour l'instant mis à null , enfin la méthode meth100(...) est actuellement en tête de liste.
Figure virtuelle de l'objet délégué à ce stade :
![]()
Il est possible d'ajouter une nouvelle méthode meth101(...) au délégué qui va la mettre en tête de liste à la place de la méthode meth100(...) qui devient le deuxième élément de la liste. C# utilise l'opérateur d'addition pour implémenter l'ajout d'une nouvelle méthode au délégué. Nous étendons le programme précédent :
delegate int Deleguer ( char x );
class ClasseA {
public int meth100 ( char x ) {
System.Console.WriteLine ("Exécution de meth100('"+x+"')");
return x+100 ;
}
public int meth101 ( char x ) {
System.Console.WriteLine ("Exécution de meth101('"+x+"')");
return x+101 ;
}
static void Main ( string [] args ) {
ClasseA ObjA = new ClasseA( );
//-- meth100 est en tête de liste :
Deleguer FoncDeleg = new Deleguer ( ObjA.meth100 ) ;
// meth101 est ajoutée en tête de liste devant meth100 :
FoncDeleg = FoncDeleg + new Deleguer ( ObjA.meth101 ) ;
}
}
Figure virtuelle de l'objet délégué à cet autre stade :
![]()
C# permet de consulter et d'utiliser si nous le souhaitons toutes les référencement de méthodes en nous renvoyant la liste dans une tableau de référence de type Delegate grâce à la méthode GetInvocationList. Le source ci-dessous retourne dans le tableau Liste, la liste d'appel dans l'ordre d'appel, du délégué FoncDeleg :
Delegate[ ] Liste = FoncDeleg.GetInvocationList( ) ;
1.6 Exécution des méthodes d'un délégué multicast - Exemple de code
Lorsque l'on invoque le délégué sur un paramètre effectif, C# appelle et exécute séquentiellement les méthodes contenues dans la liste jusqu'à épuisement. L'ordre d'appel est celui du stockage : la première stockée est exécutée en premier, la suivante après, la dernière méthode ajoutée est exécutée en dernier, s'il y a un résultat de retour, c'est celui de la dernière méthode ajoutée qui est renvoyé , les autres résultats de retour sont ignorés. L'exemple ci-dessous reprend les notions que nous venons d'exposer.
Source complet exécutable d'un exemple de délégué multicast :
namespace PrDelegate {
delegate int Deleguer ( char x );
class ClasseA {
public int meth100 ( char x ) {
System.Console.WriteLine ("Exécution de meth100('"+x+"')");
return x+100 ;
}
public int meth101 ( char x ) {
System.Console.WriteLine ("Exécution de meth101('"+x+"')");
return x+101 ;
}
public int meth102 ( char x ) {
System.Console.WriteLine ("Exécution de meth102('"+x+"')");
return x+102 ;
}
public static int meth103 ( char x ) {
System.Console.WriteLine ("Exécution de meth103('"+x+"')");
return x+103 ;
}
static void Main ( string [] args ) {
System.Console.WriteLine ("Un délégué référence ObjA.meth1 :" ) ;
ClasseA ObjX , ObjA = new ClasseA( );
//-- instanciation du délégué avec ajout de 4 méthodes :
Deleguer FoncDeleg = new Deleguer ( ObjA.meth100 ) ;
FoncDeleg += new Deleguer ( ObjA.meth101 ) ;
FoncDeleg += new Deleguer ( ObjA.meth102 ) ;
FoncDeleg += new Deleguer ( meth103 ) ;
//--la méthode meth103 est en tête de liste :
ObjX = (ClasseA)FoncDeleg.Target ;
if (ObjX == null System.Console.WriteLine ("Méthode static, Target = null") ;
else if (ObjX.Equals(ObjA))System .Console.WriteLine ("Target référence bien ObjA") ;
else System.Console.WriteLine ("Target ne référence pas ObjA") ;
System.Console.WriteLine ( "\nnom : "+FoncDeleg.Method.Name );
System.Console.WriteLine ( "classe : "+FoncDeleg.Method.DeclaringType.ToString( ) ) ;
System.Console.WriteLine ( "retour : "+FoncDeleg.Method.ReturnType.ToString( ) );
//--Appel du délégué sur le paramètre effectif 'a' :
ObjA.champ = FoncDeleg('a') ; //code ascii 'a' = 97
System.Console.WriteLine ( "\nvaleur du champ : "+ObjA.champ) ;
System.Console.WriteLine ( "----------------------------------") ;
//-- Parcours manuel de la liste des méthodes référencées :
Delegate[ ] Liste = FoncDeleg.GetInvocationList( ) ;
foreach ( Delegate Elt in Liste )
{
ObjX = (ClasseA)Elt.Target ;
if (ObjX == null) System.Console.WriteLine ("Méthode static, Target = null") ;
else if (ObjX.Equals(ObjA))System .Console.WriteLine ("Target référence bien ObjA") ;
else System.Console.WriteLine ("Target ne référence pas ObjA") ;
System.Console.WriteLine ( "\nnom : "+Elt.Method.Name ) ;
System.Console.WriteLine ( "classe : "+Elt.Method.DeclaringType.ToString( ) ) ;
System.Console.WriteLine ( "retour : "+Elt.Method.ReturnType.ToString( ) ) ;
System.Console.WriteLine ( "----------------------------------") ;
}
System.Console.ReadLine ( ) ;
}
}
}
Résultats d'exécution sur la console :
![]()
Nous voyons bien que le délégué FoncDeleg contient la liste des référencement des méthodes meth100, meth101,meth102 et meth103 ordonné comme figuré ci-dessous :
![]()
Remarquons que le premier objet de la liste est une référence sur une méthode de classe, la propriété Target renvoie la valeur null (le champ _target est à null).
La méthode de classe meth103 ajoutée en dernier est bien en tête de liste :
![]()
L'invocation du délégué lance l'exécution séquentielle des 4 méthodes :
![]()
et le retour du résutat est celui de meth103('a') :
![]()
Le parcours manuel de la liste montre bien que ce sont des objets de type Delegate qui sont stockés et que l'on peut accéder entre autre possibilités, à leurs propriétés :
![]()