Modèles d'événement faible

Dans les applications, il est possible que les gestionnaires attachés à des 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, en fournissant une classe de gestionnaire dédiée pour des événements particuliers et en implémentant une interface sur les écouteurs pour cet événement. Ce modèle de conception est appelé modèle d’événement faible.

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 classique d’écoute d’un événement consiste à utiliser la syntaxe spécifique au langage qui attache un gestionnaire à un événement sur une source. Par exemple, en C#, cette syntaxe est : source.SomeEvent += new SomeEventHandler(MyEventHandler).

Cette technique crée une référence forte de la source d’événement à l’écouteur d’événement. En règle générale, l’attachement d’un gestionnaire d’événements pour un écouteur entraîne la suppression explicite de l’écouteur d’une durée de vie d’objet influencée par la durée de vie de l’objet de la source (sauf si le gestionnaire d’événements est explicitement supprimé). Toutefois, 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, comme s’il appartient actuellement à l’arborescence visuelle de l’application, et non par la durée de vie de la source. Chaque fois que la durée de vie de l’objet source dépasse la durée de vie de l’objet de l’écouteur, le modèle d’événement normal entraîne une fuite de mémoire : l’écouteur est conservé plus longtemps que prévu.

Le modèle d’événement faible est conçu pour résoudre ce problème de fuite de mémoire. Le modèle d’événement faible peut être utilisé chaque fois qu’un écouteur doit s’inscrire à un événement, mais l’écouteur ne sait pas explicitement quand annuler l’inscription. Le modèle d’événement faible peut également être utilisé chaque fois que 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 devez implémenter le modèle d’événement faible ?

L’implémentation du modèle d’événement faible est intéressante principalement pour les auteurs de contrôles. 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 des objets de 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 complètement 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 trois façons d’implémenter un modèle d’événement faible. Le tableau suivant répertorie les trois approches et fournit des conseils pour chaque utilisation.

Approche Quand implémenter
Utiliser une classe de gestionnaire d’événements faible existante Si l’événement auquel vous souhaitez vous abonner dispose d’un gestionnaire d’événements WeakEventManagerfaible existant, utilisez le 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 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.
Utiliser une classe de gestionnaire d’événements faible générique Utilisez un générique WeakEventManager<TEventSource,TEventArgs> lorsqu’un existant WeakEventManager n’est pas disponible, que vous souhaitez implémenter facilement et que vous n’êtes pas préoccupé par l’efficacité. Le générique WeakEventManager<TEventSource,TEventArgs> est moins efficace qu’un gestionnaire d’événements faible existant ou personnalisé. Par exemple, la classe générique effectue davantage de réflexion pour découvrir l’événement en fonction du nom de l’événement. En outre, le code permettant d’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.
Créer une classe de gestionnaire d’événements faible personnalisée Créez une valeur personnalisée WeakEventManager lorsqu’un existant WeakEventManager n’est pas disponible et que vous souhaitez optimiser l’efficacité. L’utilisation d’un code personnalisé WeakEventManager pour s’abonner à un événement sera plus efficace, mais vous entraînez le coût d’écriture d’un plus grand nombre de code au début.
Utiliser un gestionnaire d’événements faible tiers NuGet dispose de plusieurs gestionnaires d’événements faibles et de nombreux frameworks WPF prennent également en charge le modèle.

Les sections suivantes décrivent comment implémenter le modèle d’événement faible. À des fins de cette discussion, 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 EventSource classe.

  • Le gestionnaire d’événements a le type : SomeEventEventHandler (ou EventHandler<SomeEventEventArgs>).

  • L’événement transmet un paramètre de type SomeEventEventArgs aux gestionnaires d’événements.

Utilisation d’une classe 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 dans 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.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Remplacez-le par le modèle suivant :

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    De même, si votre code utilise le modèle suivant pour se désabonner d’un événement :

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    Remplacez-le par le modèle suivant :

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Utilisation de la classe Gestionnaire d’événements faibles générique

  1. 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 et appelez AddHandler comme indiqué dans le code suivant :

    WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
    

Création d’une classe Gestionnaire d’événements faible personnalisée

  1. Copiez le modèle de classe suivant dans votre projet.

    Cette classe 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(EventSource source,
                                      EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(EventSource source,
                                         EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("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<SomeEventEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
  2. Remplacez le SomeEventWeakEventManager nom par votre propre nom.

  3. Remplacez les trois noms décrits précédemment par les noms correspondants pour votre événement. (SomeEvent, et EventSourceSomeEventEventArgs)

  4. Définissez la visibilité (public/ interne/ privé) de la classe de gestionnaire d’événements faible sur la même visibilité que l’événement qu’elle gère.

  5. 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 SomeEventEventHandler(OnSomeEvent);
    

    Remplacez-le par le modèle suivant :

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    De même, si votre code utilise le modèle suivant pour se désabonner d’un événement :

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Remplacez-le par le modèle suivant :

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Voir aussi