Udostępnij przez


Słabe wzorce zdarzeń (WPF .NET)

W aplikacjach możliwe jest, że programy obsługi dołączone do źródeł zdarzeń nie zostaną zniszczone w koordynacji z obiektem odbiornika, który dołączył procedurę obsługi do źródła. Taka sytuacja może prowadzić do przecieków pamięci. Program Windows Presentation Foundation (WPF) wprowadza wzorzec projektu, którego można użyć do rozwiązania tego problemu. Wzorzec projektu udostępnia dedykowaną klasę menedżera dla określonych zdarzeń i implementuje interfejs na odbiornikach dla tego zdarzenia. Ten wzorzec projektu jest nazywany słabym wzorcem zdarzeń.

Ważne

Dokumentacja przewodnika dla komputerów dla platform .NET 7 i .NET 6 jest w budowie.

Wymagania wstępne

W tym artykule przyjęto założenie, że masz podstawową wiedzę na temat zdarzeń kierowanych i zapoznasz się z omówieniem zdarzeń trasowanych. Aby postępować zgodnie z przykładami w tym artykule, warto zapoznać się z językiem Extensible Application Markup Language (XAML) i wiedzieć, jak pisać aplikacje programu Windows Presentation Foundation (WPF).

Dlaczego warto zaimplementować słaby wzorzec zdarzenia?

Nasłuchiwanie zdarzeń może prowadzić do przecieków pamięci. Zwykłą techniką nasłuchiwania zdarzenia jest użycie składni specyficznej dla języka w celu dołączenia programu obsługi do zdarzenia w źródle. Na przykład instrukcja source.SomeEvent += new SomeEventHandler(MyEventHandler) języka C# lub instrukcja AddHandler source.SomeEvent, AddressOf MyEventHandlerVB . Jednak ta technika tworzy silne odwołanie ze źródła zdarzeń do odbiornika zdarzeń. Jeśli program obsługi zdarzeń nie zostanie jawnie wyrejestrowany, okres istnienia obiektu odbiornika będzie mieć wpływ na okres istnienia obiektu źródła. W pewnych okolicznościach można chcieć, aby okres istnienia obiektu odbiornika był kontrolowany przez inne czynniki, takie jak to, czy obecnie należy do drzewa wizualnego aplikacji. Za każdym razem, gdy okres istnienia obiektu źródła wykracza poza okres istnienia użytecznego obiektu odbiornika, odbiornik jest utrzymywany dłużej niż jest to konieczne. W takim przypadku nieprzydzielona ilość pamięci do przecieku pamięci.

Słaby wzorzec zdarzeń został zaprojektowany w celu rozwiązania problemu z wyciekiem pamięci. Słaby wzorzec zdarzenia może być używany, gdy odbiornik musi zarejestrować się na potrzeby zdarzenia, ale odbiornik nie wie jawnie, kiedy wyrejestrować. Słaby wzorzec zdarzenia może być również używany, gdy okres istnienia obiektu źródła przekracza użyteczny okres istnienia obiektu odbiornika. W takim przypadku przydatne jest określenie przez Ciebie. Wzorzec słabego zdarzenia umożliwia odbiornikowi rejestrowanie i odbieranie zdarzenia bez wpływu na charakterystykę okresu istnienia obiektu odbiornika w jakikolwiek sposób. W efekcie dorozumiane odwołanie ze źródła nie określa, czy odbiornik kwalifikuje się do odzyskiwania pamięci. Odwołanie jest słabym odwołaniem, w związku z czym nazewnictwo słabego wzorca zdarzeń i powiązanych interfejsów API. Odbiornik może być odśmiecany lub w inny sposób zniszczony, a źródło może kontynuować bez zachowywania odwołań obsługi niezwiązanych z teraz zniszczonym obiektem.

KtoTo należy zaimplementować słaby wzorzec zdarzenia?

Słaby wzorzec zdarzenia jest przede wszystkim istotny dla autorów kontrolek. Jako autor kontrolki jesteś w dużej mierze odpowiedzialny za zachowanie i zawieranie kontrolki oraz wpływ na aplikacje, w których jest wstawiony. Obejmuje to zachowanie okresu istnienia obiektu kontrolki, w szczególności obsługę opisanego problemu z przeciekiem pamięci.

Niektóre scenariusze z natury nadają się do stosowania słabego wzorca zdarzeń. Jednym z takich scenariuszy jest powiązanie danych. W powiązaniu danych często obiekt źródłowy jest niezależny od obiektu odbiornika, który jest obiektem docelowym powiązania. Wiele aspektów powiązania danych WPF ma już zastosowany słaby wzorzec zdarzeń w sposób implementowania zdarzeń.

Jak zaimplementować słaby wzorzec zdarzeń

Istnieją cztery sposoby implementowania słabego wzorca zdarzeń, a każde podejście używa innego menedżera zdarzeń. Wybierz menedżera zdarzeń, który najlepiej odpowiada Twojemu scenariuszowi.

  • Istniejący słaby menedżer zdarzeń:

    Użyj istniejącej słabej klasy menedżera zdarzeń, gdy zdarzenie, które chcesz zasubskrybować, ma odpowiadający WeakEventManagerelement . Aby uzyskać listę słabych menedżerów zdarzeń dołączonych do platformy WPF, zobacz hierarchię WeakEventManager dziedziczenia w klasie. Ponieważ dołączone słabe menedżery zdarzeń są ograniczone, prawdopodobnie musisz wybrać jedno z innych podejść.

  • Ogólny słaby menedżer zdarzeń:

    Użyj ogólnego WeakEventManager<TEventSource,TEventArgs> , jeśli istniejący WeakEventManager nie jest dostępny i szukasz najprostszego sposobu implementowania słabych zdarzeń. Jednak ogólny WeakEventManager<TEventSource,TEventArgs> jest mniej wydajny niż istniejący lub niestandardowy słaby menedżer zdarzeń, ponieważ używa odbicia w celu odnalezienia zdarzenia z jego nazwy. Ponadto kod potrzebny do zarejestrowania zdarzenia przy użyciu funkcji ogólnej WeakEventManager<TEventSource,TEventArgs> jest bardziej szczegółowy niż użycie istniejącego lub niestandardowego WeakEventManager.

  • Niestandardowy słaby menedżer zdarzeń:

    Tworzenie niestandardowego WeakEventManager , gdy istniejący WeakEventManager element nie jest dostępny, a wydajność ma kluczowe znaczenie. Chociaż bardziej wydajny niż ogólny WeakEventManager, niestandardowy WeakEventManager wymaga pisania większej liczby kodów z góry.

  • Słaby menedżer zdarzeń innych firm:

    Użyj słabego menedżera zdarzeń innej firmy, jeśli potrzebujesz funkcji, które nie są udostępniane przez inne podejścia. NuGet ma niektórych słabych menedżerów zdarzeń. Wiele struktur WPF obsługuje również wzorzec.

W poniższych sekcjach opisano sposób implementowania słabego wzorca zdarzeń za pomocą różnych typów menedżera zdarzeń. W przypadku ogólnych i niestandardowych słabych przykładów menedżera zdarzeń zdarzenie do zasubskrybowania ma następujące cechy.

  • Nazwa zdarzenia to SomeEvent.
  • Zdarzenie jest wywoływane przez klasę SomeEventSource .
  • Procedura obsługi zdarzeń ma typ EventHandler<SomeEventArgs>.
  • Zdarzenie przekazuje parametr typu SomeEventArgs do programów obsługi zdarzeń.

Używanie istniejącej słabej klasy menedżera zdarzeń

  1. Znajdź istniejącego słabego menedżera zdarzeń. Aby uzyskać listę słabych menedżerów zdarzeń dołączonych do platformy WPF, zobacz hierarchię WeakEventManager dziedziczenia klasy.

  2. Użyj nowego słabego menedżera zdarzeń zamiast normalnego podpinania zdarzeń.

    Jeśli na przykład kod używa następującego wzorca do subskrybowania zdarzenia:

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

    Zmień go na następujący wzorzec:

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

    Podobnie, jeśli kod używa następującego wzorca do anulowania subskrypcji zdarzenia:

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

    Zmień go na następujący wzorzec:

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

Używanie ogólnej słabej klasy menedżera zdarzeń

Użyj klasy ogólnej WeakEventManager<TEventSource,TEventArgs> zamiast normalnego podpinania zdarzeń.

Gdy używasz WeakEventManager<TEventSource,TEventArgs> do rejestrowania odbiorników zdarzeń, należy podać źródło zdarzenia i EventArgs typ jako parametry typu do klasy. Wywołaj metodę AddHandler , jak pokazano w poniższym kodzie:

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

Tworzenie niestandardowej słabej klasy menedżera zdarzeń

  1. Skopiuj następujący szablon klasy do projektu. Następująca klasa dziedziczy z WeakEventManager klasy:

    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. Zmień nazwę SomeEventWeakEventManager, SomeEvent, SomeEventSourcei SomeEventArgs na zgodną z nazwą zdarzenia.

  3. Ustaw modyfikatory dostępu dla słabej klasy menedżera zdarzeń, aby pasować do ułatwień dostępu zarządzanego zdarzenia.

  4. Użyj nowego słabego menedżera zdarzeń zamiast normalnego podpinania zdarzeń.

    Jeśli na przykład kod używa następującego wzorca do subskrybowania zdarzenia:

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

    Zmień go na następujący wzorzec:

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

    Podobnie, jeśli kod używa następującego wzorca do anulowania subskrypcji zdarzenia:

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

    Zmień go na następujący wzorzec:

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

Zobacz też