Delen via


Zwakke gebeurtenispatronen (WPF .NET)

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. Het ontwerppatroon biedt een toegewezen managerklasse voor bepaalde gebeurtenissen en implementeert een interface op listeners voor die gebeurtenis. Dit ontwerppatroon wordt het zwakke gebeurtenispatroongenoemd.

Voorwaarden

In het artikel wordt ervan uitgegaan dat u basiskennis hebt van gerouteerde gebeurtenissen en dat u overzicht van gerouteerde gebeurtenissen hebt gelezen. Als u de voorbeelden in dit artikel wilt volgen, helpt dit als u bekend bent met Extensible Application Markup Language (XAML) en weet hoe u WPF-toepassingen (Windows Presentation Foundation) schrijft.

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 taalspecifieke syntaxis om een handler te koppelen aan een gebeurtenis op een bron. De C#-uitdrukking source.SomeEvent += new SomeEventHandler(MyEventHandler) of de VB-uitdrukking AddHandler source.SomeEvent, AddressOf MyEventHandler. Deze techniek maakt echter een sterke verwijzing van de gebeurtenisbron naar de gebeurtenislistener. Tenzij de gebeurtenis-handler expliciet niet is geregistreerd, wordt de levensduur van het object van de listener beïnvloed door de levensduur van het object van de bron. In bepaalde omstandigheden wilt u mogelijk dat de levensduur van het object van de listener wordt bepaald door andere factoren, zoals of deze momenteel deel uitmaakt van de visuele structuur van de toepassing. Wanneer de levensduur van het object van de bron langer is dan de levensduur van het nuttige object van de listener, blijft de listener langer in leven dan nodig is. In dit geval is het niet-toegewezen geheugen een geheugenlek.

Het zwakke gebeurtenispatroon is ontworpen om het geheugenlekprobleem 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 listener in aanmerking komt voor garbagecollection. De verwijzing is een zwakke verwijzing, dus de naam van het zwakke gebeurtenispatroon en de bijbehorende API's. De listener kan worden verzameld of anderszins vernietigd en de bron kan doorgaan zonder niet-verzamelbare handlerverwijzingen naar een nu vernietigd object te behouden.

Wie moet het zwakke gebeurtenispatroon implementeren?

Het zwakke gebeurtenispatroon is voornamelijk relevant voor controleauteurs. Als auteur van een besturingselement bent u grotendeels verantwoordelijk voor het gedrag en de insluiting van uw besturingselement en de impact ervan op de toepassingen waarin het wordt ingevoegd. Dit omvat het gedrag van de objectlevensduur van het besturingselement, 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 onafhankelijk is van het listenerobject, wat 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 zwakke gebeurtenispatroon implementeren

Er zijn vier manieren om het zwakke gebeurtenispatroon te implementeren en elke benadering maakt gebruik van een andere gebeurtenismanager. Selecteer de gebeurtenisbeheerder die het beste bij uw scenario past.

  • Bestaande zwakke gebeurtenisbeheerder:

    Gebruik een bestaande zwakke event manager-klasse wanneer de gebeurtenis waarop u zich wilt abonneren, een bijbehorende WeakEventManagerheeft. 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.

  • Algemene zwakke gebeurtenisbeheerder:

    Gebruik een algemene WeakEventManager<TEventSource,TEventArgs> wanneer een bestaande WeakEventManager niet beschikbaar is en u op zoek bent naar de eenvoudigste manier om zwakke gebeurtenissen te implementeren. De algemene WeakEventManager<TEventSource,TEventArgs> is echter minder efficiënt dan de bestaande of aangepaste zwakke gebeurtenisbeheerder omdat deze gebruikmaakt van reflectie om de gebeurtenis op basis van de naam te vinden. Bovendien is de code die nodig is om de gebeurtenis te registreren met behulp van de algemene WeakEventManager<TEventSource,TEventArgs> uitgebreider dan wanneer gebruik wordt gemaakt van een bestaande of aangepaste WeakEventManager.

  • Aangepaste zwakke gebeurtenisbeheerder:

    Maak een aangepaste WeakEventManager wanneer een bestaande WeakEventManager niet beschikbaar is en efficiëntie cruciaal is. Hoewel efficiënter dan een algemene WeakEventManager, moet u voor een aangepaste WeakEventManager meer vooraf code schrijven.

  • zwakke gebeurtenissenbeheerder van derden:

    Gebruik een zwakke gebeurtenisbeheerder van derden wanneer u functionaliteit nodig hebt die niet door de andere methoden wordt geleverd. Er zijn in NuGet enkele zwakke eventmanagers. Veel WPF-frameworks ondersteunen ook het patroon.

In de volgende secties wordt beschreven hoe u het zwakke gebeurtenispatroon implementeert door gebruik te maken van de verschillende gebeurtenisbeheertypen. Voor de algemene en aangepaste voorbeelden van zwakke gebeurtenismanagers heeft de gebeurtenis waarop u zich wilt abonneren de volgende kenmerken.

  • De naam van de gebeurtenis is SomeEvent.
  • De gebeurtenis wordt geactiveerd door de klasse SomeEventSource.
  • De evenement-handler heeft type EventHandler<SomeEventArgs>.
  • De gebeurtenis geeft een parameter van het type SomeEventArgs door aan de gebeurtenis-handlers.

Een bestaande zwakke event manager-klasse gebruiken

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

  2. Gebruik de nieuwe zwakke eventmanager in plaats van de normale eventaansluiting.

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

    source.LostFocus += new RoutedEventHandler(Source_LostFocus);
    
    AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Wijzig dit in het volgende patroon:

    LostFocusEventManager.AddHandler(source, Source_LostFocus);
    
    LostFocusEventManager.AddHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

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

    source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
    
    RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Wijzig dit in het volgende patroon:

    LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
    
    LostFocusEventManager.RemoveHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

Gebruik de algemene zwakke event manager klasse

Gebruik de generieke WeakEventManager<TEventSource,TEventArgs>-class in plaats van de normale eventkoppeling.

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

WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
    source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))

Een aangepaste zwakke event manager-klasse maken

  1. Kopieer de volgende klassesjabloon naar uw project. De volgende klasse neemt de WeakEventManager klasse over:

    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. Wijzig de naam van SomeEventWeakEventManager, SomeEvent, SomeEventSourceen SomeEventArgs zodat deze overeenkomt met de naam van uw gebeurtenis.

  3. Stel de toegangsmodifiers in voor de zwakke event manager-klasse zodat deze overeenkomt met de toegankelijkheid van de gebeurtenis die wordt beheerd.

  4. 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 EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Wijzig dit in het volgende patroon:

    SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.AddHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

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

    source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Wijzig dit in het volgende patroon:

    SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.RemoveHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

Zie ook