33
6 Délégués, événements et interfaces de rappel L’essentiel de ce chapitre porte sur les différentes techniques que propose .NET Framework pour permettre aux objets de votre système d’établir des communications bidirectionnelles. En premier lieu, vous découvrirez l’utilité du mot-clé VB .NET Delegate. Ses capacités dépassent quelque peu celles du simple objet qui « pointe sur » les méthodes qu’il est en mesure d’appeler. Lorsque vous saurez créer et manipuler des délégués, vous explorerez le protocole de gestion des événements .NET qui repose sur le modèle de délégation. Vous constaterez que VB .NET préserve parfaitement la syntaxe de gestion des événements de VB 6.0 tout en accroissant sa flexibilité. Dans chaque application développée jusqu’ici, vous avez ajouté différents éléments de code à la méthode Main() qui, d’une manière ou d’une autre, envoyait des messages à un objet donné. Toutefois, nous n’avons pas encore étudié la manière dont un objet peut répondre à l’entité qui l’a créé. Dans la majorité des programmes, il est fréquent que les objets d’un sys- tème établissent une communication « bilatérale » par le biais d’événements, d’interfaces de rappel et d’autres structures de programmation. Pour amorcer le sujet, je parlerai brièvement de l’architecture de gestion des événements prise en charge par Visual Basic 6.0. Rappel sur la gestion des événements sous VB 6.0 Trois mots-clés Visual Basic 6.0 permettent de déclarer et d’envoyer des événements présents dans vos applications et d’y répondre : Event, RaiseEvent et WithEvents. Comme vous le savez sans doute, le protocole de gestion des événements VB 6.0 repose sur l’architecture de points

Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

  • Upload
    lamtu

  • View
    217

  • Download
    1

Embed Size (px)

Citation preview

Page 1: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

6Délégués, événementset interfaces de rappel

L’essentiel de ce chapitre porte sur les différentes techniques que propose .NET Frameworkpour permettre aux objets de votre système d’établir des communications bidirectionnelles.En premier lieu, vous découvrirez l’utilité du mot-clé VB .NET Delegate. Ses capacitésdépassent quelque peu celles du simple objet qui « pointe sur » les méthodes qu’il est enmesure d’appeler. Lorsque vous saurez créer et manipuler des délégués, vous explorerez leprotocole de gestion des événements .NET qui repose sur le modèle de délégation. Vousconstaterez que VB .NET préserve parfaitement la syntaxe de gestion des événements deVB 6.0 tout en accroissant sa flexibilité.

Dans chaque application développée jusqu’ici, vous avez ajouté différents éléments de code àla méthode Main() qui, d’une manière ou d’une autre, envoyait des messages à un objetdonné. Toutefois, nous n’avons pas encore étudié la manière dont un objet peut répondre àl’entité qui l’a créé. Dans la majorité des programmes, il est fréquent que les objets d’un sys-tème établissent une communication « bilatérale » par le biais d’événements, d’interfaces derappel et d’autres structures de programmation. Pour amorcer le sujet, je parlerai brièvementde l’architecture de gestion des événements prise en charge par Visual Basic 6.0.

Rappel sur la gestion des événements sous VB 6.0Trois mots-clés Visual Basic 6.0 permettent de déclarer et d’envoyer des événements présentsdans vos applications et d’y répondre : Event, RaiseEvent et WithEvents. Comme vous le savezsans doute, le protocole de gestion des événements VB 6.0 repose sur l’architecture de points

Page 2: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET250

Copyright ©

2002 Éditions E

yrolles

de connexion COM. Ces mots-clés intrinsèques masquent heureusement la complexité d’uneutilisation directe des points de connexion COM et prennent en charge le mécanisme sous-jacent nécessaire. Certes, les événements VB 6.0 sont naturellement adaptés aux élémentsGUI (Graphical User Interface) Windows, mais rien ne vous empêche de recourir à des événe-ments qui utilisent des types de classes standards non GUI. Par exemple, supposons la défini-tion de classe VB 6.0 suivante :

Sous VB 6.0, le mot-clé Event permet de définir un événement dont le déclenchement peutêtre différé à l’aide du mot-clé RaiseEvent. Lorsque l’utilisateur de l’objet appelle la sous-routine TriggerEvent(), l’objet déclenche l’événement SendMessage à quiconque se trouve àl’écoute.

À présent, imaginez que vous souhaitiez créer une instance du type TheEventClass dans unefeuille VB 6.0 et recevoir l’événement entrant SendMessage. La première étape obligatoireconsiste à déclarer la variable d’objet à l’aide de l’objet WithEvents. Vous devez ensuite cons-truire un gestionnaire d’événements (parfois appelé collecteur d’événements) qui sera appelélors du déclenchement de l’événement.

Les gestionnaires d’événements VB 6.0 obéissent à une syntaxe très particulière,EventObjVariable_EventName. Ainsi, dans notre exemple, si vous créez une instance du typeTheEventClass (nommée ec), le gestionnaire de l’événement SendMessage devra avoir pournom ec_SendMessage et être défini à l’aide du même nombre et des mêmes types de paramètresentrants. Voici le code complet :

' Definition de la classe TheEventClass (VB 6.0).Public Event SendMessage(ByVal s As String)

' Lorsque l'utilisateur de l'objet appelle cette méthode, l'événement est déclenché.Public Sub TriggerEvent() RaiseEvent SendMessage("Message de l'événement")End Sub

' Principale définition de la feuille (VB 6.0).Option ExplicitPrivate WithEvents ec As TheEventClassPrivate Sub Form_Load() Set ec = New TheEventClassEnd SubPrivate Sub btnTriggerEvent_Click() ec.TriggerEventEnd Sub

Page 3: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

251

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Lorsque vous exécutez ce programme, le message présenté à la figure 6-1 s’affiche.

Tour d’horizon de la gestion des événements VB .NETLe schéma de gestion des événements de VB 6.0 présente un inconvénient. En effet, peud’éléments indiquent qu’un gestionnaire d’événements donné est le destinataire d’un événe-ment entrant. Il est vrai que les développeurs VB 6.0 expérimentés sont capables d’analyserfacilement la convention de nommage EventObjVariable_EventName. Il n’en reste pas moinsque rien n’identifie de manière unique ces sous-routines aux noms étranges en tant quegestionnaire d’événements. Cette situation peut s’avérer problématique pour répondre à desévénements envoyés par un élément GUI Windows.

Par exemple, si vous gérez l’événement Click d’un objet Button avant de renommer lavariable Button dans la fenêtre Propriétés, le gestionnaire d’événement généré a pour nomCommand1_Click(). En revanche, si vous modifiez le nom de ce type Button (disons enbtnClickMe), l’événement entrant n’est plus géré car VB recherche désormais un gestionnaired’événement nommé btnClickMe_Click(). À ce stade, vous seul pouvez décider de déplacermanuellement la logique de l’événement du gestionnaire précédent dans le nouveau.

Le langage VB devrait, dans l’idéal, fournir un mot-clé spécifique qui lie une méthode à unévénement par programmation. C’est la raison pour laquelle VB .NET fournit le mot-cléHandles. Par exemple, si vous développez l’application VB6Events précédente sous VB .NET,la logique des événements du formulaire sera la suivante :

Private Sub ec_SendMessage(ByVal s As String) MsgBox s, , "Message envoyé depuis TheEventClass"End Sub

Figure 6-1Message reçu.

SOURCE

Le projet VB6Events est situé dans le répertoire du chapitre 6.

Public Class Form1 Inherits System.Windows.Forms.Form ' Remarque! Vous POUVEZ associer New sur la même ligne ' que 'WithEvents' sous VB .NET (au contraire de VB 6.0).

Page 4: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET252

Copyright ©

2002 Éditions E

yrolles

Vous constatez que VB .NET poursuit la prise en charge du mot-clé WithEvents. Le codeHandles placé après la liste d’arguments de la méthode du gestionnaire d’événements cons-titue une nouvelle amélioration. L’instruction Handles est qualifiée par le nom de l’événementlié à la méthode concernée.

L’IDE VS .NET prend toujours en charge la liste déroulante d’événements de type VB. Celle-ci génère automatiquement le code stub du gestionnaire d’événements pour toute variabledéclarée au moyen du mot-clé WithEvents (figure 6-2).

Le mot-clé Handles est particulièrement appréciable car il lie syntaxiquement une méthode,dont le nom importe peu, à un événement (émis par un objet). Ainsi, même si l’IDE nommele gestionnaire d’événements en recourant à la syntaxe de type VB 6.0,EventObjVariable_EventName, vous pouvez la modifier.

Private WithEvents ec As New TheEventClass() Friend WithEvents Button1 As System.Windows.Forms.Button . . . ' Lie l'événement Click de Button1 à la méthode Button1_Click. Private Sub ThisIsTheButton1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click ec.TriggerEvent() End Sub 'Lie l'événement SendMessage de TheEventClass à la méthode ec_SendMessage. Private Sub ec_SendMessage(ByVal s As String) _ Handles ec.SendMessage MessageBox.Show(s, "Message envoyé depuis TheEventClass") End SubEnd Class

Figure 6-2L’IDE VS .NET continue de générer automatiquement le code stub d’événements.

Page 5: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

253

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Quant à la définition du type TheEventClass, les choses sont pratiquement identiques avecVB .NET :

Conception d’une interface d’événementsOutre les mots-clés VB 6.0 dédiés aux événements, vous connaissez sans doute la notion dedéfinition et d’implémentation « d’interfaces de rappel ». Solution de substitution au proto-cole officiel des points de connexion COM, cette technique permet à un client de recevoir desévénements d’une classe par le biais d’une interface personnalisée. Pour illustrer le fonction-nement d’interfaces comme mécanismes de rappel, nous allons étudier leur création sousVB .NET (un procédé pratiquement identique à celui utilisé avec VB 6.0).

En premier lieu, considérons le type Car défini au chapitre 4. Nous voulons désormaisinformer les utilisateurs de l’imminence de l’explosion du moteur (lorsque la vitesse du véhi-cule atteint 10 km/h au-dessous de la vitesse maximale) ainsi que du moment où elle survient,et ce non pas en levant une exception personnalisée, mais en déclenchant un événement. Danscet exercice, nous n’utiliserons pas le mot-clé Event ou WithEvent, mais l’interface personna-lisée suivante :

Private Sub ThisIsTheMethodCalledByTheEventClass(ByVal s As String) _ Handles ec.SendMessage MessageBox.Show(s, "Message envoyé depuis TheEventClass")End Sub

Public Class TheEventClass ' La syntaxe VB .NET des événements. Public Event SendMessage(ByVal s As String) Public Sub TriggerEvent() RaiseEvent SendMessage("Message de l'événement") End SubEnd Class

INFO CODE SOURCE

Le projet SimpleVBNetEvents est situé dans le répertoire du chapitre 6.

' L'interface d'événement du moteur.Public Interface IEngineEvents Sub AboutToBlow(ByVal msh As String) Sub Exploded(ByVal msg As String)End Interface

Page 6: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET254

Copyright ©

2002 Éditions E

yrolles

Cette interface est implémentée par un collecteur d’événements côté client que le type Carpourra appeler lors du déclenchement d’un certain événement. Voici une mise en œuvrepossible :

Désormais, un objet implémente l’interface d’événements. Votre travail consiste maintenant àpasser une référence à ce collecteur dans le type Car. Ce dernier conserve la référence etrappelle le collecteur si nécessaire.

Vous devez ajouter une interface publique au type Car pour lui permettre d’obtenir une réfé-rence au collecteur. Nous appellerons cette méthode Advise(). Pour rompre avec la sourced’événements, l’utilisateur de l’objet peut appeler une autre méthode, Unadvise(). En outre, letype Car tient à jour un type ArrayList qui représente chacune des connexions en attente afinde permettre à l’utilisateur de l’objet d’enregistrer plusieurs collecteurs d’événements. Voiciles modifications à apporter au type Car :

Public Class CarEventSink Implements IEngineEvents Private name As String Public Sub New(ByVal sinkName As String) name = sinkName End Sub Public Sub AboutToBlow(ByVal msg As String) _ Implements IEngineEvents.AboutToBlow Console.WriteLine("{0} signale : {1}", name, msg) End Sub Public Sub Exploded(ByVal msg As String) _ Implements IEngineEvents.exploded Console.WriteLine("{0} signale : {1}", name, msg) End SubEnd Class

Public Class Car ' Le tableau des collecteurs connectés. Private itfConnections As ArrayList = New ArrayList() ' Attacher ou déconnecter de la source des événements. Public Sub Advise(ByVal itfClientImpl As IEngineEvents) itfConnections.Add(itfClientImpl) End Sub Public Sub Unadvise(ByVal itfClientImpl As IEngineEvents) itfConnections.Remove(itfClientImpl) End Sub. . .End Class

Page 7: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

255

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

À ce stade, la méthode Car.SpeedUp() peut être améliorée pour parcourir la liste desconnexions et déclencher la notification ad hoc (remarquez que vous avez supprimé le code degestion des exceptions antérieur) :

L’utilisation d’interfaces d’événements permet au client de contrôler le moment exact de lacréation (et de la destruction) du collecteur d’événements. Car est désormais configuré pourtenir à jour la liste des collecteurs connectés. Voici à présent un code côté client qui permet decréer la connexion :

' Protocole des événements basé sur l'interface !Public Class Car. . . Public Sub SpeedUp(ByVal delta As Integer) ' Si la voiture est hors-service, on l'indique... If (dead) Then Dim e As IEngineEvents Dim i As Integer For i = 0 To itfConnections.Count - 1 e = CType(itfConnections(i), IEngineEvents) e.Exploded("Désolé, la voiture est hors-service...") Next Else currSpeed += delta ' Presque hors-service ? If (10 = maxSpeed - currSpeed) Then Dim e As IEngineEvents Dim i As Integer For i = 0 To itfConnections.Count - 1 e = CType(itfConnections(i), IEngineEvents) e.AboutToBlow("Attention, vitesse maximale proche !") Next End If ' Toujours OK ! If (currSpeed >= maxSpeed) Then dead = True Else Console.WriteLine("->Vitesse actuelle = {0}", currSpeed) End If End If End SubEnd Class

Page 8: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET256

Copyright ©

2002 Éditions E

yrolles

La figure 6-3 affiche le résultat.

Ce modèle d’interface d’événements fonctionnerait aussi sous VB 6.0. Toutefois, la construc-tion et l’implémentation d’une telle interface sous VB 6.0 présentent un inconvénient majeur :le phénomène redoutable du comptage de références circulaires. Par exemple, l’objet Carconserve une référence à chaque collecteur côté client et le client une référence à l’objet Car.Par conséquent, aucun objet ne peut être détruit tant que l’un d’eux n’a pas préalablementabandonné sa référence à l’autre. La plate-forme .NET résout le dilemme du comptage de

' Créer une voiture et écouter les événements.Module Module1 Sub Main() ' Construire une voiture comme d'habitude. Dim c1 As Car = New Car("SlugBug", 100, 10) ' Créer 2 objets collecteur. Dim sink As CarEventSink = New CarEventSink("Premier collecteur") Dim myOtherSink As CarEventSink = New CarEventSink("Autre collecteur") ' Passer les collecteurs à Car. c1.Advise(sink) c1.Advise(myOtherSink) ' Accélérer (Ceci va générer des événements.) Dim i As Integer For i = 0 To 10 c1.SpeedUp(20) Next ' Détacher le collecteur des événements. Console.WriteLine("—>Détachement de sink. . .") c1.Unadvise(sink) ' Accélérer à nouveau (seul myothersink sera appelé.) For i = 0 To 10 c1.SpeedUp(20) Next ' Détacher l'autre collecteur des événements. Console.WriteLine("—>Détachement de myOtherSink. . .") c1.Unadvise(myOtherSink) ' Accélérer à nouveau (aucun envoi d'événements. . .) For i = 0 To 10 c1.SpeedUp(20) Next Console.WriteLine("Terminé.") End SubEnd Module

Page 9: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

257

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

références circulaires. En effet, le schéma du ramasse-miettes (étudié au chapitre 4) n’utiliseaucun décompte de référence COM.

En résumé, les mots-clés Event, RaiseEvent et WithEvents sont compatibles sous VB 6.0 etVB .NET. Ils prennent également en charge les interfaces de rappel pour autoriser les commu-nications bidirectionnelles. En fait, le protocole de gestion des événements de VB .NET n’estqu’une version épurée de celui de son prédécesseur. Les choses sont toutefois foncièrementdifférentes en coulisses. Il est évident, sachant que l’infrastructure .NET n’est pas le modèleCOM, que ces mots-clés n’utilisent plus le protocole de points de connexion COM. En réalité,ces mots-clés familiers ont fait l’objet d’une considérable mise à niveau interne en vue defonctionner avec l’architecture de gestion des événements .NET. La pierre angulaire de cetteopération est un petit concept appelé délégué.

Mot-clé VB .NET DelegateAvant de définir de façon formelle les délégués .NET, il est nécessaire de prendre un peu derecul. D’un point de vue historique, l’interface de programmation d’applications (API, Appli-cation Programming Interface) Windows utilise fréquemment des pointeurs sur des fonctionsde type C pour créer des entités appelées « fonctions de rappel » ou plus simplement« rappels ». Ces rappels permettent aux développeurs de configurer une fonction donnée afinqu’elle fasse son rapport auprès d’une autre fonction de l’application, autrement dit qu’elle la« rappelle ». Il faut comprendre que les fonctions de rappel de style C n’ont rien à voir avec

Figure 6-3Des interfaces en guise de protocole d’événements.

INFO CODE SOURCE

Le projet EventInterface est situé dans le répertoire du chapitre 6.

Page 10: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET258

Copyright ©

2002 Éditions E

yrolles

les points de connexion COM traditionnels, ni avec les interfaces d’événements (même si,dans une large mesure, ils conduisent au même résultat final).

Malheureusement, les fonctions de rappel C standards ne représentent qu’une adresse brute enmémoire. Dans l’idéal, la configuration des rappels devrait intégrer des informations sécuri-sées supplémentaires, notamment le nombre de paramètres (et leurs types) et la valeur deretour éventuelle de la méthode pointée. Ce n’est malheureusement pas le cas avec les fonc-tions de rappel traditionnelles ; une situation qui, vous vous en doutez, est source de fréquentsincidents à l’exécution, tels que plantages systèmes et autres bogues.

Les rappels n’en demeurent pas moins des entités utiles que l’infrastructure .NET autorise.Leur fonctionnalité s’y applique d’une manière bien plus sûre et bien plus orientée objet àl’aide de délégués. Un délégué est essentiellement un objet qui pointe sur une autre méthodeprésente dans l’application. En termes plus précis, un délégué tient à jour trois informationsimportantes :

1. Le nom de la méthode qu’il appelle.

2. Les (éventuels) arguments de cette méthode.

3. La valeur de retour (éventuelle) de cette méthode.

Une fois créé et muni des informations présentées ci-avant, le délégué peut appeler dynami-quement la méthode qu’il représente au moment de l’exécution.

Pour créer un délégué sous VB .NET, vous utilisez le mot-clé Delegate. En interne, ce mot-clése développe pour représenter une classe issue de System.MulticastDelegate. Supposons parexemple le code VB .NET suivant :

Le compilateur VB .NET produit une nouvelle classe nommée PlayAcidHouse dérivée de laclasse System.MulticastDelegate. Cette classe peut uniquement appeler une méthode quiaccepte deux paramètres (de type System.Object et System.Int32) et ne renvoie rien. À lafigure 6-4, l’utilitaire ILDasm.exe montre le type de classe PlayAcidHouse sous forme de méta-données. Vous pouvez constater qu’il est bien dérivé de la classe System.MulticastDelegate.

La classe PlayAcidHouse se voit dotée de trois méthodes publiques. La principale méthode estsans aucun doute Invoke(). Vous pouvez l’appeler pour informer le délégué qu’il est tempsd’appeler la méthode sur laquelle il pointe. Remarquez que les paramètres transmis à Invoke()sont identiques à ceux de la déclaration du délégué PlayAcidHouse. Les méthodes BeginIn-voke() et EndInvoke() permettent d’appeler la méthode courante de manière asynchrone (aucontraire d’invoke() qui réalise des appels de méthode synchrones). Nous allons simplementnous concentrer sur le comportement synchrone du type MulticastDelegate.

Public Delegate Sub PlayAcidHouse(ByVal PaulOakenfold As Object, _ByVal volume As Integer)

Page 11: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

259

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Membres de la classe System.MulticastDelegateLorsque vous utilisez le mot-clé Delegate, vous déclarez indirectement un type dérivé de laclasse System.MulticastDelegate. Le type MulticastDelegate dérive lui-même de la classeSystem.Delegate. Combinées, ces deux classes de base offrent l’infrastructure nécessaire pourreprésenter et appeler une méthode à la volée. Le tableau 6-1 illustre certains membres héritésqu’il est intéressant de connaître.

Comme vous pouvez le déduire à la lecture du tableau 6-1, les délégués Multicast peuventpointer sur plusieurs méthodes. Le type System.Delegate définit en fait une liste liée qui sertà conserver chacune des méthodes qu’un délégué donné tient à jour. La méthode Combine()permet d’ajouter des méthodes à cette liste interne. La méthode Remove(), quant à elle, permet

Figure 6-4Le mot-clé VB .NET Delegate représente un type dérivé de la classe System.Multicast Delegate.

Tableau 6-1 – Quelques membres hérités

Membre hérité Description

Method Cette propriété renvoie le nom d’une méthode partagée tenue à jour par le délégué.

Target Si la méthode à appeler est définie au niveau de l’objet (plutôt qu’en tant que méthode partagée), ce membre renvoie le nom de la méthode tenue à jour par le délégué. Si la valeur renvoyée par Target est Nothing, la méthode à appeler est une méthode partagée.

Combine() Cette méthode partagée ajoute une méthode à la liste tenue à jour par le délégué.

GetInvocationList() Renvoie un tableau de types Delegate. Chacun d’eux représente une méthode particulière qu’il est possible d’appeler.

Remove() Cette méthode partagée supprime un délégué des méthodes énumérées à appeler.

Page 12: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET260

Copyright ©

2002 Éditions E

yrolles

d’en supprimer. La capacité d’un délégué à appeler plusieurs fonctions se nomme« multidiffusion » (multicasting). Nous aborderons cette technique sous peu dans ce chapitre.

Le plus simple des exemplesLes délégués constituent l’épine dorsale du modèle de gestion des événements .NET. Ainsi,utiliser le mot-clé VB .NET Event revient à créer un nouveau délégué. Lorsque vous envoyezun événement à l’aide du mot-clé RaiseEvent, le code de langage intermédiaire (IL, Interme-diate Language) sous-jacent appelle automatiquement System.MulticastDelegate.Invoke()en votre nom. Même si VB .NET masque ces détails, les délégués n’en demeurent pas moinsdes éléments syntaxiques particulièrement utiles. Poursuivons leur examen par un exempletrès simple :

Il faut d’abord déclarer un type de délégué au moyen du mot-clé VB .NET Delegate. Ledélégué AnyMethodWhichTakesAString représente un objet. Cet objet maintient une référence àune méthode qui accepte un seul et unique paramètre String et qui ne renvoie rien.

Le mot-clé VB .NET AdressOf permet d’affecter la cible (c’est-à-dire, la méthode à appeler)d’un délégué donné. Ce mot-clé renvoie une toute nouvelle instance du type System.Multi-castDelegate, configurée pour pointer sur la méthode spécifiée. Lorsqu’il faut informer ledélégué qu’il est temps d’appeler ladite méthode, il ne reste plus qu’à appeler la méthodeInvoke() :

Module Module1 ' Voici les méthodes qui seront appelées ' par les délégués. Public Sub PlainPrint(ByVal msg As String) Console.WriteLine("Le message est : {0}", msg) End Sub ' Définir le délégué. Public Delegate Sub AnyMethodTakingAString(ByVal s As String) Sub Main() ' Creation du délégué. Dim del As AnyMethodTakingAString del = AddressOf PlainPrint del.Invoke("Bonjour tout le monde. . .") ' Afficher quelques données sur le délégué. Console.WriteLine("Je viens juste d'appeler : {0}", del.Method) End SubEnd Module

Page 13: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

261

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Par ailleurs, rappelez-vous que vous pouvez spécifier l’adresse de la méthode à appelercomme paramètre de constructeur. Par conséquent, vous pouvez déclarer et configurer ledélégué en une seule et simple ligne de code, comme suit :

Quelle que soit la manière de définir la cible du délégué, vous obtiendrez le résultat présentéà la figure 6-5.

Lorsque vous générez la méthode tenue à jour par le délégué (à l’aide de la propriété Method),vous constatez que chaque paramètre (dans le cas présent, le paramètre VB .NET String) estautomatiquement associé au type système .NET correspondant. Remarquez également laprésence du mot-clé Void. Comme vous le savez, créer une sous-routine VB .NET revientessentiellement à créer une fonction sans valeur de retour. Vous savez également que beau-coup d’autres langages (tels que C#, C++ et Java) ne font aucune distinction entre fonction etsous-routine ; au lieu de cela, ils utilisent le type de données void pour représenter desméthodes sans valeur de retour. Ainsi, le Void présent ici n’est que la représentation interned’une sous-routine VB .NET (c’est-à-dire, une méthode qui ne renvoie rien).

Un délégué se soucie peu du nom de la méthode qu’il est chargé d’appeler. Si vous lesouhaitez, vous pouvez modifier dynamiquement la méthode cible de la manière suivante :

' Signaler au délégué la méthode à appeler,' et l'exécuter !del = AddressOf PlainPrintdel.Invoke("Bonjour tout le monde. . .")

' Création du délégué (et définir la cible).Dim del As AnyMethodTakingAString = _ New AnyMethodTakingAString(AddressOf PlainPrint)

Figure 6-5Délégation de l’appel d’une méthode.

Module Module1 ' Le délégué va maintenant appeler chacune de ces méthodes. ' Remarquez que la signature de chaque méthode est identique. Public Sub PlainPrint(ByVal msg As String) Console.WriteLine("Le message est : {0}", msg) End Sub

Page 14: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET262

Copyright ©

2002 Éditions E

yrolles

Si vous tentez d’affecter l’adresse d’une méthode qui ne correspond pas à la déclaration desdélégués de la façon suivante :

Public Sub UpperCasePrint(ByVal msg As String) Console.WriteLine("Le message est : {0}", msg.ToUpper()) End Sub Public Sub XXXXYYYYZZZZ888777aaa(ByVal msg As String) Console.WriteLine("Le message est : {0}", msg) End Sub ' Création d'un délégué. Public Delegate Sub AnyMethodTakingAString(ByVal s As String) Sub Main() ' Créer le délégué et appeler chaque méthode. Dim del As AnyMethodTakingAString del = AddressOf PlainPrint del.Invoke("Bonjour tout le monde. . .") Console.WriteLine("Je viens juste d'appeler : {0}", del.Method) del = AddressOf UpperCasePrint del.Invoke("YoYoMa") Console.WriteLine("Je viens juste d'appeler : {0}", del.Method) del = AddressOf XXXXYYYYZZZZ888777aaa del.Invoke("Un dernier test. . .") Console.WriteLine("Je viens juste d'appeler : {0}", del.Method) End SubEnd Module

Figure 6-6« Pointage » dynamique sur plusieurs méthodes.

' Mauvaise cible !Public Sub BadTargetForDelegate(ByVal x As Integer, ByVal y As AppDomain) ' d'autres éléments.End Sub. . .' Mauvaise cible ! Erreur !del = AddressOf BadTargetForDelegatedel.Invoke("Pardon?!?!")

Page 15: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

263

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

L’erreur de compilation suivante est heureusement générée. Remarquez le texte en gras quimet en évidence le point clé de l’erreur :

Construction d’un délégué plus élaboréNous allons maintenant développer un exemple plus complexe. Commençons par modifier laclasse Car existante en y intégrant deux nouvelles variables membres Boolean. La première(isDirty) permet de déterminer si votre voiture doit être lavée ; la seconde (shouldRotate)précise si les pneus doivent être remplacés. Pour permettre à l’utilisateur de l’objet d’interagiravec ces nouvelles données d’état, la classe Car définit certaines propriétés complémentaireset un constructeur mis à jour. Voici comment se déroule la modification de la classe :

C:\Apress Books\VB .NET\Code\Chapter 6\SimpleDelegate\Module1.vb(45):Could not find method 'Public Sub BadTargetForDelegate(x As Integer,y As System.AppDomain)' with the same signature as the delegate'Delegate Sub AnyMethodTakingAString(s As String)'.

INFO CODE SOURCE

Le projet SimpleDelegate est situé dans le répertoire du chapitre 6.

' Une autre mise à jour de la classe Car.Public Class Car. . . ' NOUVEAU ! Est-ce qu'un nettoyage est nécessaire ? Doit-on changer les pneus ? Private isDirty As Boolean Private shouldRotate As Boolean ' Paramètres de constructeur supplémentaires pour initialiser les valeurs ' boléennes. Public Sub New(ByVal name As String, ByVal max As Integer, _ ByVal curr As Integer, ByVal dirty As Boolean, ByVal rotate As Boolean) currSpeed = curr maxSpeed = max petName = name dead = False isDirty = Dirty shouldRotate = rotate theMusicBox = New Radio() End Sub ' Propriétés supplémentaires pour interagir avec les valeurs booléennes. Public Property Dirty() As Boolean

Page 16: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET264

Copyright ©

2002 Éditions E

yrolles

Imaginez à présent que vous ayez déclaré le délégué suivant (qui, encore une fois, n’est riend’autre qu’un type orienté objet représentant une méthode donnée) comme un type au sein del’espace de noms racine du projet :

Vous venez de créer un délégué nommé CarDelegate. Le type CarDelegate représente « unecertaine » fonction qui accepte Car comme paramètre sans type de retour. Actuellement, letype de délégué est découplé du type Car auquel il est logiquement associé ; en effet, CarDe-legate n’est qu’un type parmi d’autres au sein de l’espace de noms racine. Même si cetteapproche n’est pas mauvaise en soi, il serait plus avisé de définir le délégué CarDelegate direc-tement au sein du type Car, pour une encapsulation maximale :

Sachant que le mot-clé VB .NET Delegate produit une nouvelle classe dérivée du typeSystem.MulticastDelegate, le délégué CarDelegate constitue désormais une définition detype imbriqué, comme vous le prouve ici encore son inspection à l’aide de l’utilitaireILDasm.exe (figure 6-7).

Get Return isDirty End Get Set(ByVal Value As Boolean) isDirty = Value End Set End Property Public Property Rotate() As Boolean Get Return shouldRotate End Get Set(ByVal Value As Boolean) shouldRotate = Value End Set End PropertyEnd Class

Public Delegate Sub CarDelegate(ByVal c As Car)

Public Class Car. . . Public Delegate Sub CarDelegate(ByVal c As Car). . .End Class

Page 17: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

265

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Utilisation de CarDelegateVous disposez à présent d’un délégué qui représente un pointeur sur une méthode. Celle-ciaccepte un type Car en tant que paramètre. Vous pouvez maintenant construire d’autresméthodes qui acceptent le délégué comme paramètre. Pour illustrer ce principe, imaginonsune nouvelle classe nommée Garage. Ce type tient à jour une collection de types Car présentedans un type ArrayList. Lors de sa création, la classe Garage remplit l’élément ArrayList aveccertains types Car initiaux.

Plus important, la classe Garage définit une méthode public ProcessCars(). Celle-ci accepteun seul et unique argument de type Car.CarDelegate (rappelez-vous que vous devez indiquerla classe d’imbrication pour référencer un type imbriqué). Dans l’implémentation de laméthode ProcessCars(), chaque type Car de la collection est passé comme paramètre de lafonction « pointée » par le paramètre du délégué. Voici la définition initiale de la classeGarage :

Figure 6-7Imbrication du délégué au sein du type Car.

Public Class Garage ' Nous disposons de quelques voitures. Private theCars As ArrayList = New ArrayList() Public Sub New() theCars.Add(New Car("Viper", 100, 0, True, False)) theCars.Add(New Car("Fred", 100, 0, False, False)) theCars.Add(New Car("BillyBob", 100, 0, False, True))

Page 18: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET266

Copyright ©

2002 Éditions E

yrolles

Lorsque l’utilisateur de l’objet appelle la méthode ProcessCars(), il envoie le nom de laméthode qui doit traiter sa requête. Pour les besoins du raisonnement, imaginez que vousdisposiez de deux membres partagés, WashCar() et RotateTires(). Considérons à présent laméthode Main() suivante :

theCars.Add(New Car("Bart", 100, 0, True, True)) theCars.Add(New Car("Stan", 100, 0, False, True)) End Sub ' Cette méthode prend un CarDelegate comme paramètre. ' Donc ! 'proc' ne fait que pointer sur une fonction. . . Public Sub ProcessCars(ByVal proc As Car.CarDelegate) ' Envoie chaque voiture dans la méthode pointée par le délégué. Dim c As Car For Each c In theCars proc(c) Next Console.WriteLine() End SubEnd Class

' Le garage délègue tout les instructions de travail à ces fonctions partagées. Module Module1 ' Une cible pour le délégué. Public Sub WashCar(ByVal c As Car) If (c.Dirty) Then Console.WriteLine("Nettoyage d'une voiture") Else Console.WriteLine("Cette voiture est déjà propre. . .") End If End Sub ' Une autre cible pour le délégué. Public Sub RotateTires(ByVal c As Car) If (c.Rotate) Then Console.WriteLine("Les pneus ont été changés") Else Console.WriteLine("Pas besoin de changer les pneus. . .") End If End Sub Sub Main() ' Création du garage. Dim g As Garage = New Garage() ' Nettoyer toutes les voitures sales.

Page 19: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

267

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Vous remarquerez que les deux méthodes partagées correspondent exactement au type dedélégué (aucune valeur de retour et un seul et unique argument Car). Par ailleurs, rappelez-vous qu’utiliser le mot-clé AddressOf revient à ajouter une méthode à la liste interne tenue àjour par le type System.MulticastDelegate. La figure 6-8 présente le résultat de l’exécution dece test.

Analyse du code de délégationComme vous pouvez le constater, la méthode Main() commence par créer une instance du typeGarage. Cette classe a été configurée pour déléguer tout le travail à d’autres fonctions parta-gées. Supposons à présent que vous rédigiez le code suivant :

Vous dites en fait la chose suivante : « ajouter la méthode WashCar() à la liste tenue à jour parle type CarDelegate, puis transmettre ce délégué à Garage.ProcessCars() ». Comme dans toutvrai garage, le travail proprement dit est délégué à un autre constituant du système. Fort de cesexplications, vous supposez que ProcessCars() a l’aspect suivant :

g.ProcessCars(AddressOf WashCar) ' Changer les pneus. g.ProcessCars(AddressOf RotateTires) End SubEnd Module

Figure 6-8Sortie du délégué.

' Nettoyer toutes les voitures sales.g.ProcessCars(AddressOf WashCar)

' CarDelegate pointe sur la fonction WashCar Public Sub ProcessCars(ByVal proc As Car.CarDelegate). . . ' Maintenant appeler la méthode pour chaque voiture.

Page 20: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET268

Copyright ©

2002 Éditions E

yrolles

Affichage de la méthode cibleLe paramètre entrant CarDelegate pointe tout simplement sur une fonction particulière.Éclaircissons cette affirmation en mettant à jour la méthode ProcessCars() afin qu’elle affichele nom de la méthode couramment pointée. Même si vous n’utilisez que des fonctions parta-gées (identifiables en consultant la propriété Method du type du délégué), l’intégration du codequi suit permet de préparer une utilisation ultérieure de méthodes au niveau de l’objet. Dansce chapitre, vous modifierez la logique du paramètre CarDelegate afin de pouvoir utiliser uneclasse auxiliaire. Vous attribuerez alors les méthodes de niveau objet de la classe auxiliaire àl’objet Delegate. La lecture de la propriété Target permet d’identifier les méthodes de niveauobjet :

À présent, le résultat devrait ressembler à celui que présente la figure 6-9.

Multidiffusion (multicasting)Rappelez-vous qu’un délégué en multidiffusion est un objet capable d’appeler un nombreindéfini de méthodes. L’exemple actuel n’a pas utilisé cette fonction. Au lieu de cela, vousavez procédé à deux appels distincts à Garage.ProcessCars(), en spécifiant à chaque fois une

Dim c As Car For Each c In theCars proc(c) ' proc(c) => CarApp.WashCar(c) Next. . .End Sub

Public Sub ProcessCars(ByVal proc As Car.CarDelegate) ' Suis-je en train d'appeler une méthode d'objet ou une méthode partagée ? If (Not proc.Target Is Nothing) Then Console.WriteLine("-->Cible : {0}", proc.Target) Else Console.WriteLine("-->La cible est une méthode partagée nommée {0}", _ proc.Method) End If ' Maintenant appeler la méthode pour chaque voiture. Dim c As Car For Each c In theCars proc(c) Next Console.WriteLine()End Sub

Page 21: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

269

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

nouvelle adresse de méthode. Pour illustrer la multidiffusion, modifiez la méthode Main() dela manière suivante :

Ici, nous commençons par créer deux objets CarDelegate. Chacun d’eux pointe sur uneméthode donnée. Ensuite, nous créons un nouveau type Delegate qui renferme les méthodessur lesquelles pointent les types wash et rotate. Ainsi, lorsque vous appelez ProcessCars(),vous transmettez en fait un délégué qui représente les adresses de deux méthodes.

L’appel de la méthode Delegate.Combine() équivaut à ajouter une nouvelle fonction à la listeinterne. Pour supprimer une méthode de cette liste, vous appelez la méthode statiqueRemove(). Le premier paramètre marque le délégué dont vous souhaitez supprimer un élément,le second marque l’élément à supprimer :

Figure 6-9Affichage de la méthode « pointée ».

' Ajouter deux methodes au délégué.Sub Main() ' Création du garage (de la même façon qu'avant). Dim g As Garage = New Garage() ' Création de deux nouveaux délégués. Dim wash As Car.CarDelegate = AddressOf WashCar Dim rotate As Car.CarDelegate = AddressOf RotateTires ' Combiner chaque déléguer dans un seul type. ' Remarquez que nous utilisons la syntaxe [. . .] VB pour spécifier ' le type System.Delegate .NET, et non le mot-clé de ce même type. Dim d As [Delegate] = [Delegate].Combine(wash, rotate) ' Envoyer le délégué en multidiffusion dans la méthode ProcessCars(). g.ProcessCars(CType(d, Car.CarDelegate))End Sub

' Ôter la méthode rotate du délégué 'd'.Dim washOnly As [Delegate] = [Delegate].Remove(d, rotate)g.ProcessCars(CType(washOnly, Car.CarDelegate))

Page 22: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET270

Copyright ©

2002 Éditions E

yrolles

Avant d’examiner le résultat de ce programme, mettons également à jour Garage.Process-Cars() de manière à afficher chaque pointeur de fonction enregistré dans la liste, à l’aide de laméthode Delegate.GetInvocationList(). Cette méthode renvoie un tableau d’objets Delegateque vous parcourez avec l’instruction For Each :

Méthodes d’instances utilisées comme rappelsActuellement, le type CarDelegate stocke des pointeurs sur des fonctions partagées. Il nes’agit en rien d’une exigence du protocole de délégation. Il est parfaitement possible de délé-guer un appel à une méthode définie sur une instance d’objet. À titre d’exemple, considéronsque les méthodes WashCar() et RotateTires() sont désormais placées dans une nouvelle classenommée ServiceDept :

Public Sub ProcessCars(ByVal proc As Car.CarDelegate) ' Où passons-nous l'appel ? Dim d As [Delegate] For Each d In proc.GetInvocationList() Console.WriteLine("***** Appel de : {0} *****", _ d.Method.ToString()) Next. . .End Sub

Figure 6-10Sortie du délégué.

' Une classe utilitaire.Public Class ServiceDept Public Sub WashCar(ByVal c As Car) If (c.Dirty) Then

Page 23: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

271

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Vous modifiez à présent la méthode Main() de la façon suivante :

Examinez le nom de la cible en figure 6-11.

Console.WriteLine("Nettoyage d'une voiture") Else Console.WriteLine("Cette voiture est déjà propre. . .") End If End Sub Public Sub RotateTires(ByVal c As Car) If (c.Rotate) Then Console.WriteLine("Les pneus ont été changés") Else Console.WriteLine("Pas besoin d'être changé. . .") End If End SubEnd Class

Sub Main() ' Création du garage. Dim g As Garage = New Garage() ' Création du service. Dim sd As ServiceDept = New ServiceDept() ' Nettoyer toutes les voitures sales. g.ProcessCars(AddressOf sd.WashCar) ' Changer les pneus. g.ProcessCars(AddressOf sd.RotateTires) ' Créer deux nouveaux délégués. Dim wash As Car.CarDelegate = AddressOf sd.WashCar Dim rotate As Car.CarDelegate = AddressOf sd.RotateTires ' Stocker le nouveau délégué en vue d'une utilisation ultérieure. Dim d As [Delegate] = [Delegate].Combine(wash, rotate) ' Envoyer les nouveaux délégués à la méthode ProcessCars(). g.ProcessCars(CType(d, Car.CarDelegate)) ' Enlever le pointeur rotate. Dim washOnly As [Delegate] = [Delegate].Remove(d, rotate) g.ProcessCars(CType(washOnly, Car.CarDelegate))End Sub

INFO CODE SOURCE

Le projet CarDelegate est situé dans le répertoire du chapitre 6.

Page 24: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET272

Copyright ©

2002 Éditions E

yrolles

Compréhension et utilisation des événementsLes délégués sont des éléments syntaxiques assez intéressants car ils permettent de résoudrele nom d’une fonction à appeler, au moment de l’exécution plutôt qu’au moment de la compi-lation. Il faut reconnaître que cette orchestration syntaxique réclame un peu d’habitude.Toutefois, la possibilité qu’un objet donné a d’en rappeler un autre s’avère particulièrementutile. VB .NET fournit donc le mot-clé Event pour simplifier l’accès à ce comportement,lorsque la flexibilité de l’exploitation directe de délégués n’est pas nécessaire.

À l’aide d’un exemple simple, nous allons reconfigurer la méthode SpeedUp() de Car afinqu’elle envoie des événements personnalisés, plutôt que la logique de rappel examinée plus tôtdans ce chapitre. Le premier événement (AboutToBlow) est envoyé lorsque la vitesse du véhi-cule atteint 10 km/h au-dessous de la vitesse maximale. Le second événement (Exploded) estenvoyé lorsque l’utilisateur tente d’accélérer un véhicule qui est déjà détruit.

Comme nous l’avons déjà vu dans ce chapitre, la construction d’une classe capable d’envoyerdes événements s’effectue en deux étapes. Vous devez d’abord définir l’événement propre-ment dit à l’aide du mot-clé VB .NET Event. Ensuite, pour déclencher l’événement, vousutilisez le mot-clé RaiseEvent et indiquez l’événement à envoyer. Voici les mises à jour appro-priées de la classe Car :

Figure 6-11Délégation à des méthodes d’instances.

Page 25: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

273

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Le déclenchement d’un événement s’effectue simplement en indiquant son nom et enenvoyant les éventuels paramètres spécifiés. Pour illustrer ceci, mettons à jour l’implémenta-tion précédente de la méthode SpeedUp() afin qu’elle envoie chaque événement de cettemanière :

Vous avez donc configuré la voiture (type Car) pour qu’elle envoie deux événements person-nalisés dans les conditions appropriées. Nous verrons sous peu l’utilisation de ce nouveauvéhicule. Mais tout d’abord, examinons l’architecture des événements .NET plus en détail.

Public Class Car. . . ' Est-ce que la voiture est en état de marche ou hors-service ? Private Dead As Boolean

' Cette voiture peut envoyer ces événements. Public Event Exploded(ByVal msg As String) Public Event AboutToBlow(ByVal msg As String) . . .End Class

Public Sub SpeedUp(ByVal delta As Integer) ' Si la voiture est hors-service, envoyer l'événement. If (iDead) Then RaiseEvent Exploded("Désolé, cette voiture est hors-service. . .") Else currSpeed += delta ' Presque hors-service ? If (10 = maxSpeed - currSpeed) Then RaiseEvent AboutToBlow("Attention, vitesse maximale proche !") End If ' Toujours OK ? If (currSpeed >= maxSpeed) Then Dead = True Else ' Afficher simplement la vitesse actuelle si la voiture est en état de 'marche. Console.WriteLine("--> Vitesse actuelle = {0}", currSpeed) End If End If End Sub

Page 26: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET274

Copyright ©

2002 Éditions E

yrolles

Fonctionnement interne des événementsUn événement (Event) VB .NET représente en réalité une somme importante d’informations.Chaque déclaration d’événement génère les informations suivantes :

1. Un nouveau délégué imbriqué et masqué est automatiquement créé, puis ajouté à votreclasse. Le nom de ce délégué possède toujours la forme suivante <Nom_événement>Event-Handler.

2. Deux fonctions publiques masquées sont automatiquement ajoutées à votre classe ; l’uneprésente le préfixe add_, l’autre le préfixe remove_. Ces fonctions sont à usage interne.Elles appellent respectivement les méthodes Delegate.Combine() et Delegate.Remove()pour ajouter et supprimer des méthodes dans la liste tenue à jour par le délégué.

3. Une nouvelle variable membre masquée est ajoutée à votre classe. Elle représente unenouvelle instance du type caché System.MulticastDelegate (voir l’étape 1).

Bien sûr, vous êtes parfaitement libre d’ignorer ce mécanisme qu’il est pourtant intéressant decomprendre (par ailleurs, n’oublions pas que les développeurs C# doivent définir manuelle-ment les délégués de leur classe, outre les événements proprement dits !). À titre d’illustra-tion, étudiez la figure 6-12 ; il s’agit d’une capture d’écran du type Car vu par ILDasm.exe.

Si vous examinez les instructions intermédiaires sous-jacentes de add_AboutToBlow(), vousdécouvrirez ce qui suit (remarquez que l’appel à Delegate.Combine() est géré en votre nom) :

La fonction remove_AboutToBlow() se charge évidemment d’appeler automatiquementDelegate.Remove() :

.method public hidebysig specialname staticvoid add_AboutToBlow(class CarEvents.Car/EngineHandler 'value') cil managed synchronized{ // Code size 22 (0x16) .maxstack 8 IL_0000: ldsfld class CarEvents.Car/EngineHandler CarEvents.Car::AboutToBlow IL_0005: ldarg.0 IL_0006: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_000b: castclass CarEvents.Car/EngineHandler IL_0010: stsfld class CarEvents.Car/EngineHandler CarEvents.Car::AboutToBlow IL_0015: ret} // end of method Car::add_AboutToBlow

Page 27: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

275

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Figure 6-12Vue interne des événements.

.method public hidebysig specialname static void remove_AboutToBlow(class CarEvents.Car/EngineHandler 'value') cil managed synchronized{ // Code size 22 (0x16) .maxstack 8 IL_0000: ldsfld class CarEvents.Car/EngineHandler CarEvents.Car::AboutToBlow IL_0005: ldarg.0 IL_0006: call class [mscorlib]System.Delegate

Page 28: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET276

Copyright ©

2002 Éditions E

yrolles

Les instructions intermédiaires des déclarations d’événements utilisent les balises [.addon] et[.removeon] pour créer les méthodes add_XXX et remove_XXX appropriées :

L’étude du code intermédiaire sous-jacent de l’itération de la méthode SpeedUp() révèle que ledélégué est appelé de votre part. Voici un extrait du langage intermédiaire :

Les mots-clés VB .NET qui régissent les événements sont donc très utiles pour construire etmanipuler des délégués bruts en votre nom ! Cependant, comme nous l’avons vu dans cechapitre, VB .NET permet également la manipulation directe de délégués. Vous savez désor-

[mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_000b: castclass CarEvents.Car/EngineHandler IL_0010: stsfld class CarEvents.Car/EngineHandler CarEvents.Car::AboutToBlow IL_0015: ret} // end of method Car::remove_AboutToBlow

.event CarEvents.Car/EngineHandler AboutToBlow{ .addon void CarEvents.Car::add_AboutToBlow(class CarEvents.Car/EngineHandler) .removeon void CarEvents.Car::remove_AboutToBlow(class CarEvents.Car/EngineHandler)} // end of event Car::AboutToBlow

.method public instance void SpeedUp(int32 delta) cil managed{ . . . IL_000a: ldfld class CarEvents.Car/ExplodedEventHandler CarEvents.Car::ExplodedEvent IL_000f: ldnull IL_0010: beq.s IL_0023 IL_0012: ldarg.0 IL_0013: ldfld class CarEvents.Car/ExplodedEventHandler CarEvents.Car::ExplodedEvent IL_0018: ldstr "Désolé, cette voiture est hors-service. . ." IL_001d: callvirt instance void CarEvents.Car/ExplodedEventHandler::Invoke(string) IL_0022: nop. . .}

Page 29: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

277

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

mais construire une classe capable d’envoyer des événements. À présent, la question impor-tante est de savoir comment configurer un objet qui recevra ces événements.

Raccordement d’événements entrants (WithEvents)Imaginez que vous ayez créé une instance de la classe Car et que vous souhaitiez rester àl’écoute des événements qu’elle pourrait envoyer. Comme nous l’avons déjà vu, les mots-clésWithEvents et Handles vous permettent de raccorder un événement. Aussi, utilisez la défini-tion du module suivant :

Cette approche est plus ou moins similaire à la logique de gestion des événements tradition-nelle de VB 6.0. Le résultat est présenté à la figure 6-13.

Module Module1 ' Utiliser WithEvents. Dim WithEvents c As New Car("NightRider", 50, 0) Public Sub MyExplodedHandler(ByVal s As String) _ Handles c.Exploded Console.WriteLine(s) End Sub Public Sub MyAboutToDieHandler(ByVal s As String) _ Handles c.AboutToBlow Console.WriteLine(s) End Sub Sub Main() Dim i As Integer For i = 0 To 10 c.SpeedUp(10) Next End SubEnd Module

Figure 6-13Gestion d’événements à l’aide du mot-clé WithEvents.

Page 30: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET278

Copyright ©

2002 Éditions E

yrolles

L’instruction Handles s’avère très pratique car elle permet de configurer plusieurs méthodespour qu’elles soient à l’écoute du même événement. Par exemple, si vous mettez votre moduleà jour de la manière suivante :

Vous vous apercevez à présent que l’événement AboutToBlow est envoyé aux deux méthodesconfigurées pour gérer Car.AboutToBlow.

Le mot-clé WithEvents sert également à déclarer des éléments GUI Windows. Par conséquent,vous pouvez configurer un événement fondé sur une interface utilisateur graphique qui seraenvoyé à plusieurs collecteurs.

Module Module1 ' Utiliser WithEvents. Dim WithEvents c As New Car("NightRider", 50, 0) Public Sub MyExplodedHandler(ByVal s As String) Handles c.Exploded Console.WriteLine(s) End Sub Public Sub MyAboutToDieHandler(ByVal s As String) Handles c.AboutToBlow Console.WriteLine("Prêt à rendre l'âme 1 : {0}", s) End Sub Public Sub MyAboutToDieHandler2(ByVal s As String) Handles c.AboutToBlow Console.WriteLine("Prêt à rendre l'âme 2 : {0}", s) End Sub Sub Main() Dim i As Integer For i = 0 To 10 c.SpeedUp(10) Next End SubEnd Module

Figure 6-14Multidiffusion d’événements à l’aide du mot-clé Handles.

Page 31: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

279

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

Raccordement dynamique des événements entrantsL’approche précédente est simple, connue et efficace dans bon nombre de cas. Toutefois,VB .NET propose une autre méthode qui permet de raccorder un événement. Il est possible dedéclarer un objet sans utiliser le mot-clé WithEvents et de lui attacher dynamiquement ungestionnaire d’événements au moment de l’exécution. Pour ce faire, vous devez, en fin deprocessus, appeler la méthode add_XXX() appropriée afin de garantir l’ajout de votre méthodeà la liste des pointeurs de fonctions maintenue par le délégué interne du type Car. Rappelez-vous que le mot-clé Event se développe pour produire, entre autres, un délégué imbriqué.Cependant, vous n’appelez pas directement les méthodes add_XXX() et remove_XXX(), maisutilisez les instructions AddHandler et RemoveHandler.

Tout comme l’exploitation directe de délégués, le recours aux instructions AddHandler et Remo-veHandler permet de spécifier une méthode partagée ou une méthode de niveau objet. Pourrendre les choses intéressantes, considérons la classe auxiliaire suivante que nous allonsutiliser pour représenter le collecteur d’événements (remarquez l’absence d’instructionHandles) :

Nous pouvons à présent construire un nouveau type Car et raccorder un nouveau gestionnaireà la volée comme suit (remarquez l’absence du mot-clé WithEvents) :

' Collecteur d'événements Car.Public Class CarEventSink ' Collecteur d'événements OnBlowUp A. Public Sub OnBlowUp(ByVal s As String) Console.WriteLine("Message en provenance de la voiture : {0}", s) End Sub ' Collecteur d'événements OnBlowUp B. Public Sub OnBlowUp2(ByVal s As String) Console.WriteLine("-->JE REPETE : {0}", s) End Sub ' Collecteur d'événements OnAboutToBlow. Public Sub OnAboutToBlow(ByVal s As String) Console.WriteLine("Message en provenance de la voiture : {0}", s) End SubEnd Class

Sub Main() Dim i as Integer ' Création d'une voiture. Dim c1 As Car = New Car("SlugBug", 100, 10) ' Création d'un objet collecteur.

Page 32: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

VB .NET et la plate-forme .NET280

Copyright ©

2002 Éditions E

yrolles

Vous constatez que l’instruction AddHandler requiert le nom de l’événement que voussouhaitez écouter, ainsi que l’adresse de la méthode qui sera appelée en cas d’envoi duditévénement. Nous avons également activé la multidiffusion étant donné que l’événementExploded se trouve associé à deux méthodes distinctes du collecteur. L’instruction RemoveHan-dler fonctionne de la même manière. Le résultat de cet exemple est présenté en figure 6-15.

À ce stade, vous êtes en droit de vous demander quand, voire si, l’utilisation des instructionsAddHandler et RemoveHandler sera nécessaire, sachant que VB .NET conserve la prise encharge de la syntaxe WithEvents. Ici encore, il faut comprendre que cette approche est

Dim sink As CarEventSink = New CarEventSink() ' Rattacher aux événements en utilisant des gestionnaires. AddHandler c1.Exploded, AddressOf sink.OnBlowUp AddHandler c1.Exploded, AddressOf sink.OnBlowUp2 AddHandler c1.AboutToBlow, AddressOf sink.OnAboutToBlow ' Accélérer (ceci va générer les événements.) For i = 0 To 10 c1.SpeedUp(20) Next ' Détacher des événements en utilisant les gestionnaires. RemoveHandler c1.Exploded, AddressOf sink.OnBlowUp RemoveHandler c1.Exploded, AddressOf sink.OnBlowUp2 RemoveHandler c1.AboutToBlow, AddressOf sink.OnAboutToBlow ' Pas de réponse ! For i = 0 To 10 c1.SpeedUp(20) Next End SubEnd Module

Figure 6-15Gestion du jeu d’événements de votre véhicule.

Page 33: Délégués, nements et interfaces de rappel · PDF fileprotocole de gestion des événements ... de VB 6.0 présente un inconvénient. En ... qui permet de créer la connexion :

Délégués, événements et interfaces de rappelCHAPITRE 6

281

Cop

yrig

ht ©

200

2 É

ditio

ns E

yrol

les

particulièrement puissante car elle permet de découpler la source de l’événement à l’envi.L’utilisation du mot-clé WithEvent implique la réception continuelle d’événements émanantde l’objet source jusqu’à destruction de ce dernier (c’est-à-dire généralement jusqu’à lafermeture de l’application cliente). L’instruction RemoveHandler permet simplement dedemander à l’objet de cesser tout envoi de l’événement concerné, même si cet objet esttoujours actif en mémoire.

INFO CODE SOURCE

Le projet CarEvent est situé dans le répertoire du chapitre 6.