Słabe wzorce zdarzeń
W aplikacjach istnieje możliwość, że obsługiwacze dołączone do źródeł zdarzeń nie będą niszczone równocześnie z obiektem nasłuchującym, który dołączył obsługiwacz do źródła. 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 projektowy jest znany jako słaby wzorzec zdarzenia.
Dlaczego implementować wzorzec słabego 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 silną referencję ze źródła zdarzeń do słuchacza 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 nasłuchujący musi zarejestrować się do zdarzenia, ale nasłuchujący nie wie dokładnie, kiedy się 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 są określane 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 słuchacz kwalifikuje się do zbierania śmieci. Odwołanie jest słabym odwołaniem, stąd nazewnictwo wzorca zdarzeń opartego na słabych referencjach i powiązanych 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.
Kto powinien implementować słaby wzorzec zdarzenia?
Implementowanie słabego wzorca zdarzeń jest interesujące przede wszystkim dla autorów elementów sterujących. Jako autor kontrolki, jesteś głównie odpowiedzialny za jej zachowanie i ograniczenie oraz wpływ na działanie aplikacji, w których jest wstawiana. 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ć.
Podejście | Kiedy należy zaimplementować |
---|---|
Używanie istniejącej słabej klasy menedżera zdarzeń | Jeśli zdarzenie, które chcesz zasubskrybować, ma odpowiedni WeakEventManager, 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ę dziedziczenia w klasie WeakEventManager. Ponieważ uwzględnione słabe menedżery zdarzeń są ograniczone, prawdopodobnie trzeba będzie wybrać jedno z innych podejść. |
Użyj generycznego słabego menedżera zdarzeń | Użyj ogólnego WeakEventManager<TEventSource,TEventArgs>, gdy istniejąca WeakEventManager jest niedostępna, chcesz mieć łatwy sposób implementacji i nie martwisz się o wydajność. Ogólny WeakEventManager<TEventSource,TEventArgs> jest mniej wydajny niż istniejący lub niestandardowy słaby menedżer zdarzeń. Na przykład klasa ogólna wykonuje więcej odbić w celu odkrywania zdarzenia na podstawie jego nazwy. Ponadto, kod do zarejestrowania zdarzenia przy użyciu ogólnego WeakEventManager<TEventSource,TEventArgs> jest bardziej rozwlekły niż przy użyciu istniejącego lub niestandardowego WeakEventManager. |
Tworzenie niestandardowej słabej klasy menedżera zdarzeń | Utwórz niestandardowy WeakEventManager, gdy istniejąca WeakEventManager jest niedostępna i chcesz uzyskać najlepszą wydajność. Użycie niestandardowego WeakEventManager do subskrybowania zdarzenia będzie bardziej wydajne, ale wiąże się z koniecznością napisania większej ilości kodu na początku. |
Korzystanie ze słabego menedżera zdarzeń innej firmy | NuGet ma kilku słabych menedżerów zdarzeń, a wiele platform WPF również obsługuje ten wzorzec. |
W poniższych sekcjach opisano sposób implementowania słabego wzorca zdarzeń. Na potrzeby tej dyskusji wydarzenie, które należy zasubskrybować, ma następujące cechy.
Nazwa zdarzenia to
SomeEvent
.Zdarzenie jest wywoływane przez klasę
EventSource
.Procedura obsługi zdarzeń ma typ:
SomeEventEventHandler
(lubEventHandler<SomeEventEventArgs>
).Zdarzenie przekazuje parametr typu
SomeEventEventArgs
do programów obsługi zdarzeń.
Używanie istniejącej słabej klasy menedżera zdarzeń
Znajdź istniejącego menedżera zdarzeń o słabej wydajności.
Aby uzyskać listę słabych menedżerów zdarzeń dołączonych do platformy WPF, zobacz hierarchię dziedziczenia w klasie WeakEventManager.
Użyj nowego słabego menedżera zdarzeń zamiast normalnego podłączania 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 uniwersalnej klasy menedżera zdarzeń typu Weak
Użyj ogólnej klasy WeakEventManager<TEventSource,TEventArgs> zamiast zwykłego połączenia zdarzeń.
Jeśli 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 klasy słabego menedżera zdarzeń
Skopiuj następujący szablon klasy do projektu.
Ta klasa dziedziczy z klasy WeakEventManager.
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); } }
Zastąp nazwę
SomeEventWeakEventManager
własną nazwą.Zastąp trzy nazwy opisane wcześniej odpowiednimi nazwami zdarzenia. (
SomeEvent
,EventSource
iSomeEventEventArgs
)Ustaw widoczność klasy zarządzania słabymi zdarzeniami (publiczna/wewnętrzna/prywatna) na taką samą widoczność, jaką ma zarządzane zdarzenie.
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ż
- WeakEventManager
- IWeakEventListener
- Przegląd zdarzeń routowanych
- Omówienie powiązania danych
.NET Desktop feedback