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 znany jako słaby wzorzec zdarzeń.
Warunki wstępne
Artykuł zakłada podstawową znajomość zdarzeń trasowanych i że przeczytałeś Przegląd 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 przypisania procedury obsługi zdarzenia w źródle. Na przykład instrukcja języka C# source.SomeEvent += new SomeEventHandler(MyEventHandler)
lub instrukcja VB AddHandler source.SomeEvent, AddressOf MyEventHandler
. Jednak ta technika tworzy silną referencję ze źródła zdarzeń do nasłuchiwacza zdarzeń. Jeśli program obsługi zdarzeń nie zostanie jawnie wyrejestrowany, czas życia obiektu nasłuchiwacza będzie zależny od czasu życia obiektu źródła. W pewnych okolicznościach możesz chcieć, aby czas życia 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 pamięć prowadzi do wycieku 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 słuchacz musi zarejestrować się na zdarzenie, ale nie wie dokładnie, kiedy się 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 tym przypadku przydatne zależy od 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, dlatego mówimy o nazwaniu wzorca słabych zdarzeń i powiązanych interfejsach 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 zaimplementować słaby wzorzec zdarzenia?
Słaby wzorzec zdarzenia jest przede wszystkim istotny dla twórców kontrolek. Jako autor kontrolki jesteś w dużej mierze odpowiedzialny za jej działanie i ograniczenie oraz za wpływ, jaki ma na aplikacje, w które jest wstawiana. Obejmuje to zachowanie cyklu życia obiektu kontrolki, w szczególności zarządzanie opisanym problemem wycieku 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 w WPF ma już zastosowany słaby wzorzec zdarzeń w sposobie, w jaki są zaimplementowane zdarzenia.
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 niewydolny menedżer zdarzeń:
Użyj istniejącej słabej klasy menedżera zdarzeń, gdy zdarzenie, które chcesz zasubskrybować, ma odpowiadający WeakEventManager. Aby uzyskać listę słabych menedżerów zdarzeń dołączonych do platformy WPF, zobacz hierarchię dziedziczenia w klasie
WeakEventManager
. Ponieważ dołączone słabe menedżery zdarzeń są ograniczone, prawdopodobnie musisz wybrać jedno z pozostałych podejść.Słaby ogólny menedżer zdarzeń:
Użyj uniwersalnego WeakEventManager<TEventSource,TEventArgs>, gdy istniejąca WeakEventManager jest niedostępna i szukasz najprostszego sposobu implementacji słabych zdarzeń. Jednak generyczny
WeakEventManager<TEventSource,TEventArgs>
jest mniej wydajny niż istniejący lub niestandardowy słaby menedżer zdarzeń, ponieważ używa refleksji w celu odnalezienia zdarzenia na podstawie jego nazwy. Ponadto kod potrzebny do zarejestrowania zdarzenia przy użyciu ogólnegoWeakEventManager<TEventSource,TEventArgs>
jest bardziej szczegółowy niż użycie istniejącego lub niestandardowegoWeakEventManager
.niestandardowego słabego menedżera zdarzeń:
Utwórz niestandardowy WeakEventManager, gdy istniejąca
WeakEventManager
nie jest dostępna, a wydajność ma kluczowe znaczenie. Chociaż bardziej wydajny niż ogólnyWeakEventManager
, niestandardowyWeakEventManager
wymaga napisania więcej kodu na początku.zewnętrzny słaby menedżer zdarzeń:
Użyj słabego menedżera zdarzeń innej firmy, jeśli potrzebujesz funkcji, które nie są udostępniane przez inne podejścia. Pakiet NuGet ma kilku 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 przykładów ogólnych i niestandardowych słabego menedżera zdarzeń, zdarzenie, na które się subskrybuje, ma następujące cechy.
- Nazwa zdarzenia to
SomeEvent
. - Zdarzenie jest wywoływane przez klasę
SomeEventSource
. - Handler 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ń
Znajdź istniejącego słabego menedżera zdarzeń. Aby uzyskać listę słabych menedżerów zdarzeń dołączonych do platformy WPF, przejrzyj hierarchię dziedziczenia klasy WeakEventManager.
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żyj ogólnej klasy słabego menedżera zdarzeń
Użyj ogólnej klasy WeakEventManager<TEventSource,TEventArgs> zamiast standardowego wiązania zdarzeń.
Jeśli używasz WeakEventManager<TEventSource,TEventArgs>
do rejestrowania odbiorników wydarzeń, należy podać źródło wydarzenia i typ EventArgs jako typy parametrów klasy. Wywołaj 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 klasy słabego menedżera zdarzeń
Skopiuj następujący szablon klasy do projektu. Następująca 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(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
Zmień nazwę
SomeEventWeakEventManager
,SomeEvent
,SomeEventSource
iSomeEventArgs
na zgodną z nazwą zdarzenia.Ustaw modyfikatory dostępu dla słabej klasy menedżera zdarzeń, aby dopasować dostępność zarządzanego zdarzenia.
Użyj nowego słabego menedżera zdarzeń zamiast zwykłego łączenia 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ż
- WeakEventManager
- IWeakEventListener
- Przegląd zdarzeń trasowanych
- Omówienie powiązania danych
.NET Desktop feedback