Sdílet prostřednictvím


Vzory slabých událostí

V aplikacích je možné, že obslužné rutiny připojené ke zdrojům událostí nebudou zničeny v koordinaci s objektem naslouchacího procesu, který připojil obslužnou rutinu ke zdroji. Tato situace může vést k nevracení paměti. Windows Presentation Foundation (WPF) zavádí vzor návrhu, který lze použít k vyřešení tohoto problému. Vzor návrhu poskytuje vyhrazenou třídu manažera pro konkrétní události a implementuje rozhraní pro naslouchací procesy pro danou událost. Tento vzor návrhu se označuje jako slabý vzor události.

Požadavky

Tento článek předpokládá základní znalost trasovaných událostí a že jste si přečetli přehled trasovaných událostí. Pokud chcete postupovat podle příkladů v tomto článku, pomůže vám to, pokud znáte jazyk XAML (Extensible Application Markup Language) a víte, jak psát aplikace WINDOWS Presentation Foundation (WPF).

Proč implementovat slabý vzor událostí?

Naslouchání událostem může vést k nevracení paměti. Obvyklou technikou naslouchání události je použití syntaxe specifické pro jazyk k připojení obslužné rutiny k události ve zdroji. Například příkaz source.SomeEvent += new SomeEventHandler(MyEventHandler) jazyka C# nebo příkaz AddHandler source.SomeEvent, AddressOf MyEventHandlerVB . Tato technika však vytvoří silný odkaz ze zdroje událostí na naslouchací objekt události. Pokud není funkce obsluhy události explicitně zrušena, bude životnost objektu posluchače ovlivněna životností objektu zdroje. Za určitých okolností můžete chtít, aby životnost posluchače byla řízena jinými faktory, například tím, zda aktuálně patří do vizuálního stromu aplikace. Kdykoli životnost objektu zdroje přesahuje užitečnou dobu životnosti posluchače, posluchač zůstane naživu déle, než je potřeba. V tomto případě nepřidělená paměť znamená únik paměti.

Slabý vzor události je navržen tak, aby vyřešil problém s únikem paměti. Slabý vzor události lze použít, když posluchač potřebuje zaregistrovat se k události, ale posluchač přesně neví, kdy se má zrušit registraci. Slabý vzor událostí lze použít také v případě, že životnost objektu zdroje překračuje užitečnou životnost objektu naslouchacího procesu. V tomto případě rozhodujete vy, co je užitečné. Model slabých událostí umožňuje naslouchacímu procesu zaregistrovat a přijmout událost, aniž by to ovlivnilo vlastnosti životnosti objektu naslouchacího procesu jakýmkoli způsobem. Implicitní odkaz ze zdroje v podstatě neurčuje, jestli je posluchač způsobilý pro uvolňování paměti. Odkaz je slabý odkaz, a proto se takto jmenuje vzor slabých událostí a související rozhraní API. Posluchač může být uvolněn z paměti nebo jinak zničen a zdroj může pokračovat, aniž by zachoval neodstranitelné odkazy na obslužnou rutinu nyní zničeného objektu.

Kdo by měl implementovat slabý vzor událostí?

Slabý vzor událostí je primárně relevantní pro autory řízení. Jako autor ovládacího prvku jste z velké části zodpovědní za chování a omezení vašeho ovládacího prvku a dopad, který má na aplikace, do kterých se vkládá. To zahrnuje chování životnosti objektu ovládacího prvku, zejména řešení popsaného problému úniku paměti.

Některé scénáře se ze své podstaty hodí pro použití vzoru slabé události. Jedním z takových scénářů je datová vazba. V datové vazbě je běžné, že zdrojový objekt je nezávislý na objektu naslouchacího procesu, což je cíl vazby. Mnoho aspektů datové vazby WPF již má slabý vzor události použitý v tom, jak jsou události implementovány.

Implementace slabého událostního vzoru

Existují čtyři způsoby implementace slabého vzoru událostí a každý přístup používá jiného správce událostí. Vyberte manažera událostí, který nejlépe vyhovuje vašemu scénáři.

  • Stávající slabý správce událostí:

    Použijte existující slabou třídu správce událostí, pokud událost, kterou chcete přihlásit k odběru, má odpovídající WeakEventManager. Seznam slabých správců událostí, které jsou součástí WPF, naleznete v hierarchii dědičnosti ve WeakEventManager třídě. Vzhledem k tomu, že zahrnutí slabí správci událostí jsou omezeni, budete pravděpodobně muset zvolit jeden z dalších přístupů.

  • Obecný slabý správce událostí:

    Použijte obecný WeakEventManager<TEventSource,TEventArgs>, když není k dispozici existující WeakEventManager a hledáte nejjednodušší způsob, jak implementovat slabé události. Avšak obecný WeakEventManager<TEventSource,TEventArgs> je méně efektivní než stávající nebo vlastní slabý správce událostí, protože pro zjištění události podle jejího názvu využívá reflexi. Kód potřebný k registraci události pomocí obecného WeakEventManager<TEventSource,TEventArgs> kódu je také více podrobný než použití existující nebo vlastní WeakEventManager.

  • Vlastní slabý správce událostí:

    Vytvořte vlastní WeakEventManager, pokud není k dispozici existující WeakEventManager a efektivita je zásadní. I když je efektivnější než obecný WeakEventManager, vlastní WeakEventManager vyžaduje, abyste napsali více počátečního kódu.

  • Správce slabých událostí třetích stran:

    Pokud potřebujete funkce, které nejsou poskytované jinými přístupy, použijte správce slabých událostí třetí strany. NuGet má některé slabé správce událostí. Model podporuje také mnoho architektur WPF.

Následující části popisují, jak implementovat slabý vzor událostí pomocí různých typů správce událostí. Pro příklady obecného a vlastního slabého správce událostí má událost k odběru následující charakteristiky.

  • Název události je SomeEvent.
  • Událost je vyvolána SomeEventSource třídou.
  • Obslužná rutina události má typ EventHandler<SomeEventArgs>.
  • Událost předává parametr typu SomeEventArgs obslužným rutinům událostí.

Použití existující slabé třídy správce událostí

  1. Najděte existujícího slabého správce událostí. Seznam slabých správců událostí zahrnutých ve WPF najdete v hierarchii dědičnosti WeakEventManager třídy.

  2. Místo normálního připojení událostí použijte nového slabého správce událostí.

    Pokud například váš kód používá k přihlášení k odběru události následující vzor:

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

    Změňte ho na následující vzor:

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

    Podobně platí, že pokud váš kód používá následující vzor k odhlášení odběru události:

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

    Změňte ho na následující vzor:

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

Použití obecné slabé třídy správce událostí

Použijte obecnou třídu WeakEventManager<TEventSource,TEventArgs> místo normálního připojení událostí.

Při použití WeakEventManager<TEventSource,TEventArgs> k registraci posluchačů událostí zadáte zdroj události a typ EventArgs jako parametry typu ve třídě. Volejte AddHandler, jak je znázorněno v následujícím kódu:

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

Vytvoření vlastní slabé třídy správce událostí

  1. Zkopírujte do projektu následující šablonu třídy. Následující třída dědí z WeakEventManager třídy:

    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. Přejmenujte SomeEventWeakEventManager, SomeEventSomeEventSourcea SomeEventArgs podle názvu události.

  3. Nastavte modifikátory přístupu pro slabou třídu správce událostí tak, aby odpovídaly přístupnosti události, která spravuje.

  4. Místo normálního připojení událostí použijte nového slabého správce událostí.

    Pokud například váš kód používá k přihlášení k odběru události následující vzor:

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

    Změňte ho na následující vzor:

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

    Podobně platí, že pokud váš kód používá následující vzor k odhlášení odběru události:

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

    Změňte ho na následující vzor:

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

Viz také