Słabe wzorce zdarzeń

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

Dlaczego zaimplementuj słaby wzorzec zdarzenia?

Nasłuchiwanie zdarzeń może prowadzić do przecieków pamięci. Typową techniką nasłuchiwania zdarzenia jest użycie składni specyficznej dla języka, która dołącza program obsługi do zdarzenia w źródle. Na przykład w języku C#ta składnia to: source.SomeEvent += new SomeEventHandler(MyEventHandler).

Ta technika tworzy silne odwołanie ze źródła zdarzeń do odbiornika zdarzeń. Zwykle dołączanie programu obsługi zdarzeń dla odbiornika powoduje, że odbiornik ma okres istnienia obiektu, na który ma wpływ okres istnienia obiektu źródła (chyba że program obsługi zdarzeń zostanie jawnie usunięty). Jednak 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, a nie przez okres istnienia źródła. Za każdym razem, gdy okres istnienia obiektu źródłowego wykracza poza okres istnienia obiektu odbiornika, normalny wzorzec zdarzenia prowadzi do wycieku pamięci: odbiornik jest utrzymywany dłużej niż planowano.

Słaby wzorzec zdarzeń został zaprojektowany w celu rozwiązania tego problemu z przeciekiem pamięci. Słaby wzorzec zdarzenia może być używany za każdym razem, 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 za każdym razem, gdy okres istnienia obiektu źródła przekracza użyteczny okres istnienia obiektu odbiornika. (W tym 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 czy należy zaimplementować słaby wzorzec zdarzenia?

Implementowanie słabego wzorca zdarzeń jest interesujące przede wszystkim 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 sterującego, w szczególności obsługę opisanego problemu z wyciekiem 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 obiekt źródłowy jest zupełnie 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 zdarzenia

Istnieją trzy sposoby implementowania słabego wzorca zdarzeń. W poniższej tabeli wymieniono trzy podejścia i przedstawiono wskazówki dotyczące tego, kiedy należy ich używać.

Metoda Kiedy należy zaimplementować
Używanie istniejącej słabej klasy menedżera zdarzeń Jeśli zdarzenie, które chcesz zasubskrybować, ma odpowiedni WeakEventManagerelement , użyj 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 w klasie. Ponieważ uwzględnione słabe menedżery zdarzeń są ograniczone, prawdopodobnie trzeba będzie wybrać jedno z innych podejść.
Używanie ogólnej słabej klasy menedżera zdarzeń Użyj ogólnego, WeakEventManager<TEventSource,TEventArgs> gdy istniejący WeakEventManager nie jest dostępny, potrzebujesz łatwego sposobu implementacji i nie interesuje Cię wydajność. WeakEventManager<TEventSource,TEventArgs> Ogólny jest mniej wydajny niż istniejący lub niestandardowy słaby menedżer zdarzeń. Na przykład klasa ogólna wykonuje więcej odbicia w celu odnalezienia zdarzenia przy użyciu nazwy zdarzenia. Ponadto kod do zarejestrowania zdarzenia przy użyciu funkcji ogólnej WeakEventManager<TEventSource,TEventArgs> jest bardziej szczegółowy niż użycie istniejącego lub niestandardowego WeakEventManager.
Tworzenie niestandardowej słabej klasy menedżera zdarzeń Utwórz niestandardowy element WeakEventManager , gdy istniejący WeakEventManager nie jest dostępny i chcesz uzyskać najlepszą wydajność. Użycie niestandardowego WeakEventManager do subskrybowania zdarzenia będzie bardziej wydajne, ale koszty pisania więcej kodu na początku będą naliczane.
Korzystanie ze słabego menedżera zdarzeń innej firmy Pakiet NuGet ma kilka słabych menedżerów zdarzeń, a wiele struktur WPF obsługuje również wzorzec.

W poniższych sekcjach opisano sposób implementowania słabego wzorca zdarzeń. Na potrzeby tej dyskusji wydarzenie do subskrybowania ma następujące cechy.

  • Nazwa zdarzenia to SomeEvent.

  • Zdarzenie jest wywoływane przez klasę EventSource .

  • Procedura obsługi zdarzeń ma typ: SomeEventEventHandler (lub EventHandler<SomeEventEventArgs>).

  • Zdarzenie przekazuje parametr typu SomeEventEventArgs 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 w klasie.

  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.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Zmień go na następujący wzorzec:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

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

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    Zmień go na następujący wzorzec:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

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

  1. 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 zdarzeń i EventArgs typ jako parametry typu do klasy i wywołać AddHandler , jak pokazano w poniższym kodzie:

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

Tworzenie niestandardowej słabej klasy menedżera zdarzeń

  1. Skopiuj następujący szablon klasy do projektu.

    Ta 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(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. Zastąp SomeEventWeakEventManager nazwę własną nazwą.

  3. Zastąp trzy nazwy opisane wcześniej odpowiednimi nazwami zdarzenia. (SomeEvent, EventSource, i SomeEventEventArgs)

  4. Ustaw widoczność (publiczna/ wewnętrzna/prywatna) słabej klasy menedżera zdarzeń na taką samą widoczność, jak zarządzane zdarzenie.

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

    Zmień go na następujący wzorzec:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

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

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Zmień go na następujący wzorzec:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Zobacz też