Modèles d’événements faibles (WPF .NET)
Dans les applications, il est possible que les gestionnaires attachés aux sources d’événements ne soient pas détruits en coordination avec l’objet écouteur qui a attaché le gestionnaire à la source. Cette situation peut entraîner des fuites de mémoire. Windows Presentation Foundation (WPF) introduit un modèle de conception qui peut être utilisé pour résoudre ce problème. Le modèle de conception fournit une classe de gestionnaire dédiée pour des événements particuliers et implémente une interface sur les écouteurs de cet événement. Ce modèle de conception est appelé modèle d’événement faible.
Prérequis
L’article suppose une connaissance de base des événements routés et que vous avez lu la vue d’ensemble des événements routés. Pour suivre les exemples de cet article, il vous aide à connaître le langage XAML (Extensible Application Markup Language) et savoir comment écrire des applications Windows Presentation Foundation (WPF).
Pourquoi implémenter le modèle d’événement faible ?
L’écoute des événements peut entraîner des fuites de mémoire. La technique habituelle d’écoute d’un événement consiste à utiliser une syntaxe spécifique au langage pour attacher un gestionnaire à un événement sur une source. Par exemple, l’instruction source.SomeEvent += new SomeEventHandler(MyEventHandler)
C# ou l’instruction AddHandler source.SomeEvent, AddressOf MyEventHandler
VB . Toutefois, cette technique crée une référence forte de la source d’événement à l’écouteur d’événement. Sauf si le gestionnaire d’événements n’est pas inscrit explicitement, la durée de vie de l’objet de l’écouteur est influencée par la durée de vie de l’objet de la source. Dans certaines circonstances, vous souhaiterez peut-être que la durée de vie de l’objet de l’écouteur soit contrôlée par d’autres facteurs, par exemple s’il appartient actuellement à l’arborescence visuelle de l’application. Chaque fois que la durée de vie de l’objet de la source s’étend au-delà de la durée de vie de l’objet utile de l’écouteur, l’écouteur est maintenu actif plus longtemps que nécessaire. Dans ce cas, la mémoire non allouée se limite à une fuite de mémoire.
Le modèle d’événement faible est conçu pour résoudre le problème de fuite de mémoire. Le modèle d’événement faible peut être utilisé lorsqu’un écouteur doit s’inscrire à un événement, mais que l’écouteur ne sait pas explicitement quand annuler l’inscription. Le modèle d’événement faible peut également être utilisé lorsque la durée de vie de l’objet de la source dépasse la durée de vie de l’objet utile de l’écouteur. Dans ce cas, utile est déterminé par vous. Le modèle d’événement faible permet à l’écouteur de s’inscrire et de recevoir l’événement sans affecter les caractéristiques de durée de vie de l’objet de l’écouteur de quelque manière que ce soit. En effet, la référence implicite de la source ne détermine pas si l’écouteur est éligible au garbage collection. La référence est une référence faible, c’est-à-dire le nommage du modèle d’événement faible et les API associées. L’écouteur peut être récupéré ou détruit par la mémoire, et la source peut continuer sans conserver les références de gestionnaire noncollectibles à un objet maintenant détruit.
Qui doit implémenter le modèle d’événement faible ?
Le modèle d’événement faible est principalement pertinent pour les auteurs de contrôle. En tant qu’auteur de contrôle, vous êtes en grande partie responsable du comportement et de l’isolement de votre contrôle et de l’impact qu’il a sur les applications dans lesquelles il est inséré. Cela inclut le comportement de durée de vie de l’objet du contrôle, en particulier la gestion du problème de fuite de mémoire décrit.
Certains scénarios se prêtent intrinsèquement à l’application du modèle d’événement faible. L’un de ces scénarios est la liaison de données. Dans la liaison de données, il est courant que l’objet source soit indépendant de l’objet écouteur, qui est une cible d’une liaison. De nombreux aspects de la liaison de données WPF ont déjà appliqué le modèle d’événement faible dans la façon dont les événements sont implémentés.
Comment implémenter le modèle d’événement faible
Il existe quatre façons d’implémenter le modèle d’événement faible, et chaque approche utilise un gestionnaire d’événements différent. Sélectionnez le gestionnaire d’événements qui convient le mieux à votre scénario.
Gestionnaire d’événements faible existant :
Utilisez une classe de gestionnaire d’événements faible existante lorsque l’événement auquel vous souhaitez vous abonner a un correspondant WeakEventManager. Pour obtenir la liste des gestionnaires d’événements faibles inclus avec WPF, consultez la hiérarchie d’héritage dans la
WeakEventManager
classe. Étant donné que les gestionnaires d’événements faibles inclus sont limités, vous devrez probablement choisir l’une des autres approches.Gestionnaire d’événements faible générique :
Utilisez un générique WeakEventManager<TEventSource,TEventArgs> lorsqu’un existant WeakEventManager n’est pas disponible et que vous recherchez le moyen le plus simple d’implémenter des événements faibles. Toutefois, le générique
WeakEventManager<TEventSource,TEventArgs>
est moins efficace que le gestionnaire d’événements faible existant ou personnalisé, car il utilise la réflexion pour découvrir l’événement à partir de son nom. En outre, le code nécessaire pour inscrire l’événement à l’aide du génériqueWeakEventManager<TEventSource,TEventArgs>
est plus détaillé que l’utilisation d’un objet existant ou personnaliséWeakEventManager
.Gestionnaire d’événements faible personnalisé :
Créez une valeur personnalisée WeakEventManager lorsqu’un existant
WeakEventManager
n’est pas disponible et que l’efficacité est cruciale. Bien qu’il soit plus efficace qu’un génériqueWeakEventManager
, une personnaliséeWeakEventManager
vous oblige à écrire du code initial.Gestionnaire d’événements faibles tiers :
Utilisez un gestionnaire d’événements faible tiers lorsque vous avez besoin de fonctionnalités qui ne sont pas fournies par les autres approches. NuGet a des gestionnaires d’événements faibles. De nombreuses infrastructures WPF prennent également en charge le modèle.
Les sections suivantes décrivent comment implémenter le modèle d’événement faible à l’aide des différents types de gestionnaire d’événements. Pour les exemples de gestionnaire d’événements faibles génériques et personnalisés, l’événement auquel s’abonner a les caractéristiques suivantes.
- Le nom de l’événement est
SomeEvent
. - L’événement est déclenché par la
SomeEventSource
classe. - Le gestionnaire d’événements a le type
EventHandler<SomeEventArgs>
. - L’événement transmet un paramètre de type
SomeEventArgs
aux gestionnaires d’événements.
Utiliser une classe de gestionnaire d’événements faible existante
Recherchez un gestionnaire d’événements faible existant. Pour obtenir la liste des gestionnaires d’événements faibles inclus avec WPF, consultez la hiérarchie d’héritage de la WeakEventManager classe.
Utilisez le nouveau gestionnaire d’événements faible au lieu du raccordement d’événements normal.
Par exemple, si votre code utilise le modèle suivant pour s’abonner à un événement :
source.LostFocus += new RoutedEventHandler(Source_LostFocus);
AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
Remplacez-le par le modèle suivant :
LostFocusEventManager.AddHandler(source, Source_LostFocus);
LostFocusEventManager.AddHandler( source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
De même, si votre code utilise le modèle suivant pour se désabonner d’un événement :
source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
Remplacez-le par le modèle suivant :
LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
LostFocusEventManager.RemoveHandler( source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
Utiliser la classe de gestionnaire d’événements faible générique
Utilisez la classe générique WeakEventManager<TEventSource,TEventArgs> au lieu du raccordement d’événement normal.
Lorsque vous utilisez WeakEventManager<TEventSource,TEventArgs>
pour inscrire des écouteurs d’événements, vous fournissez la source et EventArgs le type d’événement en tant que paramètres de type à la classe. Appel AddHandler comme indiqué dans le code suivant :
WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
Créer une classe de gestionnaire d’événements faible personnalisée
Copiez le modèle de classe suivant dans votre projet. La classe suivante hérite de la WeakEventManager classe :
class SomeEventWeakEventManager : WeakEventManager { private SomeEventWeakEventManager() { } /// <summary> /// Add a handler for the given source's event. /// </summary> public static void AddHandler(SomeEventSource source, EventHandler<SomeEventArgs> handler) { if (source == null) throw new ArgumentNullException(nameof(source)); if (handler == null) throw new ArgumentNullException(nameof(handler)); CurrentManager.ProtectedAddHandler(source, handler); } /// <summary> /// Remove a handler for the given source's event. /// </summary> public static void RemoveHandler(SomeEventSource source, EventHandler<SomeEventArgs> handler) { if (source == null) throw new ArgumentNullException(nameof(source)); if (handler == null) throw new ArgumentNullException(nameof(handler)); CurrentManager.ProtectedRemoveHandler(source, handler); } /// <summary> /// Get the event manager for the current thread. /// </summary> private static SomeEventWeakEventManager CurrentManager { get { Type managerType = typeof(SomeEventWeakEventManager); SomeEventWeakEventManager manager = (SomeEventWeakEventManager)GetCurrentManager(managerType); // at first use, create and register a new manager if (manager == null) { manager = new SomeEventWeakEventManager(); SetCurrentManager(managerType, manager); } return manager; } } /// <summary> /// Return a new list to hold listeners to the event. /// </summary> protected override ListenerList NewListenerList() { return new ListenerList<SomeEventArgs>(); } /// <summary> /// Listen to the given source for the event. /// </summary> protected override void StartListening(object source) { SomeEventSource typedSource = (SomeEventSource)source; typedSource.SomeEvent += new EventHandler<SomeEventArgs>(OnSomeEvent); } /// <summary> /// Stop listening to the given source for the event. /// </summary> protected override void StopListening(object source) { SomeEventSource typedSource = (SomeEventSource)source; typedSource.SomeEvent -= new EventHandler<SomeEventArgs>(OnSomeEvent); } /// <summary> /// Event handler for the SomeEvent event. /// </summary> void OnSomeEvent(object sender, SomeEventArgs e) { DeliverEvent(sender, e); } }
Class SomeEventWeakEventManager Inherits WeakEventManager Private Sub New() End Sub ''' <summary> ''' Add a handler for the given source's event. ''' </summary> Public Shared Sub [AddHandler](source As SomeEventSource, handler As EventHandler(Of SomeEventArgs)) If source Is Nothing Then Throw New ArgumentNullException(NameOf(source)) If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler)) CurrentManager.ProtectedAddHandler(source, handler) End Sub ''' <summary> ''' Remove a handler for the given source's event. ''' </summary> Public Shared Sub [RemoveHandler](source As SomeEventSource, handler As EventHandler(Of SomeEventArgs)) If source Is Nothing Then Throw New ArgumentNullException(NameOf(source)) If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler)) CurrentManager.ProtectedRemoveHandler(source, handler) End Sub ''' <summary> ''' Get the event manager for the current thread. ''' </summary> Private Shared ReadOnly Property CurrentManager As SomeEventWeakEventManager Get Dim managerType As Type = GetType(SomeEventWeakEventManager) Dim manager As SomeEventWeakEventManager = CType(GetCurrentManager(managerType), SomeEventWeakEventManager) If manager Is Nothing Then manager = New SomeEventWeakEventManager() SetCurrentManager(managerType, manager) End If Return manager End Get End Property ''' <summary> ''' Return a new list to hold listeners to the event. ''' </summary> Protected Overrides Function NewListenerList() As ListenerList Return New ListenerList(Of SomeEventArgs)() End Function ''' <summary> ''' Listen to the given source for the event. ''' </summary> Protected Overrides Sub StartListening(source As Object) Dim typedSource As SomeEventSource = CType(source, SomeEventSource) AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent) End Sub ''' <summary> ''' Stop listening to the given source for the event. ''' </summary> Protected Overrides Sub StopListening(source As Object) Dim typedSource As SomeEventSource = CType(source, SomeEventSource) AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent) End Sub ''' <summary> ''' Event handler for the SomeEvent event. ''' </summary> Private Sub OnSomeEvent(sender As Object, e As SomeEventArgs) DeliverEvent(sender, e) End Sub End Class
Renommez
SomeEventWeakEventManager
,SomeEvent
,SomeEventSource
etSomeEventArgs
pour correspondre à votre nom d’événement.Définissez les modificateurs d’accès pour la classe de gestionnaire d’événements faible pour qu’elle corresponde à l’accessibilité de l’événement qu’elle gère.
Utilisez le nouveau gestionnaire d’événements faible au lieu du raccordement d’événements normal.
Par exemple, si votre code utilise le modèle suivant pour s’abonner à un événement :
source.SomeEvent += new EventHandler<SomeEventArgs>(Source_SomeEvent);
AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
Remplacez-le par le modèle suivant :
SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
SomeEventWeakEventManager.AddHandler( source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
De même, si votre code utilise le modèle suivant pour se désabonner d’un événement :
source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
Remplacez-le par le modèle suivant :
SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
SomeEventWeakEventManager.RemoveHandler( source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
Voir aussi
.NET Desktop feedback