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 MyEventHandlerVB . 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érique WeakEventManager<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érique WeakEventManager, une personnalisée WeakEventManager 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

  1. 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.

  2. 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

  1. 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
    
  2. Renommez SomeEventWeakEventManager, SomeEvent, SomeEventSourceet SomeEventArgs pour correspondre à votre nom d’événement.

  3. 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.

  4. 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