Delen via


Zwakke gebeurtenispatronen

In toepassingen is het mogelijk dat handlers die zijn gekoppeld aan gebeurtenisbronnen niet worden vernietigd in coördinatie met het listenerobject dat de handler aan de bron heeft gekoppeld. Deze situatie kan leiden tot geheugenlekken. Windows Presentation Foundation (WPF) introduceert een ontwerppatroon dat kan worden gebruikt om dit probleem op te lossen, door een speciale managerklasse voor bepaalde gebeurtenissen te bieden en een interface op listeners voor die gebeurtenis te implementeren. Dit ontwerppatroon wordt het zwakke gebeurtenispatroongenoemd.

Waarom zou u het zwakke gebeurtenispatroon implementeren?

Luisteren naar gebeurtenissen kan leiden tot geheugenlekken. De gebruikelijke techniek voor het luisteren naar een gebeurtenis is het gebruik van de taalspecifieke syntaxis die een handler koppelt aan een gebeurtenis in een bron. In C# is die syntaxis bijvoorbeeld: source.SomeEvent += new SomeEventHandler(MyEventHandler).

Deze techniek maakt een sterke verwijzing van de gebeurtenisbron naar de gebeurtenislistener. Normaal gesproken zorgt het koppelen van een gebeurtenis-handler voor een listener ervoor dat de listener een objectlevensduur heeft die wordt beïnvloed door de levensduur van het object van de bron (tenzij de gebeurtenis-handler expliciet wordt verwijderd). Maar in bepaalde omstandigheden wilt u misschien dat de levensduur van het object van de listener wordt beheerd door andere factoren, zoals of deze momenteel deel uitmaakt van de visualstructuur van de toepassing, en niet door de levensduur van de bron. Wanneer de levensduur van het bronobject langer duurt dan de levensduur van het object van de listener, leidt het normale gebeurtenispatroon tot een geheugenlek: de listener blijft langer in leven dan bedoeld.

Het zwakke gebeurtenispatroon is ontworpen om dit probleem met geheugenlekken op te lossen. Het zwakke gebeurtenispatroon kan worden gebruikt wanneer een listener zich moet registreren voor een gebeurtenis, maar de listener weet niet expliciet wanneer de registratie ongedaan moet worden. Het zwakke gebeurtenispatroon kan ook worden gebruikt wanneer de levensduur van het object van de bron de levensduur van het nuttige object van de listener overschrijdt. (In dit geval wordt nuttige door u bepaald.) Met het zwakke gebeurtenispatroon kan de listener de gebeurtenis registreren en ontvangen zonder dat dit van invloed is op de kenmerken van de levensduur van het object van de listener. In feite bepaalt de impliciete verwijzing van de bron niet of de luisteraar geschikt is voor vuilnisverzameling. De verwijzing is een zwakke verwijzing, dus de naamgeving van het zwakke gebeurtenispatroon en de bijbehorende API's. De listener kan worden opgeruimd of anderszins vernietigd, en de bron kan doorgaan zonder dat niet-verzamelbare verwijzingen naar handlers van een nu vernietigd object behouden blijven.

Wie moet het zwakke gebeurtenispatroon implementeren?

Het implementeren van het zwakke gebeurtenispatroon is vooral interessant voor auteurs van besturingselementen. Als auteur van besturingselementen bent u grotendeels verantwoordelijk voor het gedrag en de insluiting van uw besturingselement en de impact ervan op toepassingen waarin het wordt ingevoegd. Dit omvat het gedrag van de levensduur van het controleobject, met name de verwerking van het beschreven probleem met geheugenlekken.

Bepaalde scenario's lenen zich inherent voor de toepassing van het zwakke gebeurtenispatroon. Een dergelijk scenario is gegevensbinding. In gegevensbinding is het gebruikelijk dat het bronobject volledig onafhankelijk is van het listenerobject, dat een doel van een binding is. Veel aspecten van WPF-gegevensbinding hebben al het zwakke gebeurtenispatroon toegepast in de wijze waarop de gebeurtenissen worden geïmplementeerd.

Het patroon Zwakke gebeurtenissen implementeren

Er zijn drie manieren om een zwak gebeurtenispatroon te implementeren. De volgende tabel bevat de drie benaderingen en bevat enkele richtlijnen voor wanneer u deze moet gebruiken.

Aanpak Wanneer moet ik implementeren?
Een bestaande zwakke event manager-klasse gebruiken Als de gebeurtenis waarop u zich wilt abonneren een overeenkomende WeakEventManagerheeft, gebruikt u de bestaande zwakke gebeurtenisbeheerder. Zie de overnamehiërarchie in de WeakEventManager-klasse voor een lijst met zwakke gebeurtenisbeheerders die deel uitmaken van WPF. Omdat de meegeleverde zwakke gebeurtenismanagers beperkt zijn, moet u waarschijnlijk een van de andere benaderingen kiezen.
Een algemene zwakke event manager-klasse gebruiken Gebruik een algemeen WeakEventManager<TEventSource,TEventArgs> wanneer een bestaande WeakEventManager niet beschikbaar is, u een eenvoudige manier wilt implementeren en u zich geen zorgen maakt over efficiëntie. De algemene WeakEventManager<TEventSource,TEventArgs> is minder efficiënt dan een bestaande of aangepaste zwakke gebeurtenisbeheerder. De algemene klasse geeft bijvoorbeeld meer weerspiegeling om de gebeurtenis te detecteren op basis van de naam van de gebeurtenis. Bovendien is de code om de gebeurtenis te registreren met behulp van de algemene WeakEventManager<TEventSource,TEventArgs> langdradiger dan wanneer je een bestaande of aangepaste WeakEventManagergebruikt.
Een aangepaste zwakke event manager-klasse maken Maak een aangepaste WeakEventManager wanneer een bestaande WeakEventManager niet beschikbaar is en u de beste efficiëntie wilt. Het gebruik van een aangepaste WeakEventManager om u te abonneren op een gebeurtenis, is efficiënter, maar er worden wel kosten in rekening gebracht voor het schrijven van meer code aan het begin.
Een zwakke gebeurtenisbeheerder van derden gebruiken NuGet heeft verschillende zwakke event managers en veel WPF-frameworks ondersteunen ook het patroon.

In de volgende secties wordt beschreven hoe u het zwakke gebeurtenispatroon implementeert. Voor deze discussie heeft de gebeurtenis waarop u zich wilt abonneren de volgende kenmerken.

  • De naam van de gebeurtenis is SomeEvent.

  • De gebeurtenis wordt gegenereerd door de klasse EventSource.

  • De gebeurtenis-handler heeft het type: SomeEventEventHandler (of EventHandler<SomeEventEventArgs>).

  • De gebeurtenis geeft een parameter van het type SomeEventEventArgs door aan de gebeurtenis-handlers.

Een bestaande klasse zwak gebeurtenisbeheer gebruiken

  1. Zoek een bestaande zwakke gebeurtenisbeheerder.

    Zie de overnamehiërarchie in de WeakEventManager-klasse voor een lijst met zwakke gebeurtenisbeheerders die deel uitmaken van WPF.

  2. Gebruik de nieuwe zwakke gebeurtenismanager in plaats van de normale gebeurteniskoppeling.

    Als uw code bijvoorbeeld het volgende patroon gebruikt om u te abonneren op een gebeurtenis:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Wijzig dit in het volgende patroon:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    En als uw code gebruikmaakt van het volgende patroon om u af te melden voor een gebeurtenis:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    Wijzig dit in het volgende patroon:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

De generieke klasse Weak Event Manager gebruiken

  1. Gebruik de generieke WeakEventManager<TEventSource,TEventArgs>-klasse in plaats van de normale gebeurteniskoppeling.

    Wanneer u WeakEventManager<TEventSource,TEventArgs> gebruikt om gebeurtenislisteners te registreren, geeft u de gebeurtenisbron en EventArgs type op als de typeparameters voor de klasse en roept u AddHandler aan, zoals wordt weergegeven in de volgende code:

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

Een klasse voor aangepaste zwakeventbeheerderen maken

  1. Kopieer de volgende klassesjabloon naar uw project.

    Deze klasse neemt over van de WeakEventManager-klasse.

    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. Vervang de SomeEventWeakEventManager naam door uw eigen naam.

  3. Vervang de drie namen die eerder zijn beschreven door de bijbehorende namen voor uw gebeurtenis. (SomeEvent, EventSourceen SomeEventEventArgs)

  4. Stel de zichtbaarheid (openbaar/intern/privé) van de zwakke klasse event manager in op dezelfde zichtbaarheid als de gebeurtenis die wordt beheerd.

  5. Gebruik in plaats van de normale gebeurteniskoppeling de nieuwe zwakke gebeurtenismanager.

    Als uw code bijvoorbeeld het volgende patroon gebruikt om u te abonneren op een gebeurtenis:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Wijzig dit in het volgende patroon:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    En als uw code gebruikmaakt van het volgende patroon om u af te melden voor een gebeurtenis:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Wijzig dit in het volgende patroon:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Zie ook