Schwache Ereignismuster (WPF .NET)
In Anwendungen ist es möglich, dass Handler, die an Ereignisquellen angefügt sind, nicht in Verbindung mit dem Listenerobjekt zerstört werden, das den Handler an die Quelle angefügt hat. Diese Situation kann zu Speicherlecks führen. Windows Presentation Foundation (WPF) führt ein Entwurfsmuster ein, mit dem dieses Problem behoben werden kann. Das Entwurfsmuster stellt eine dedizierte Managerklasse für bestimmte Ereignisse bereit und implementiert eine Schnittstelle für Listener für dieses Ereignis. Dieses Entwurfsmuster wird als das schwache Ereignismusterbezeichnet.
Voraussetzungen
In diesem Artikel wird ein grundlegendes Wissen über geroutete Ereignisse vorausgesetzt, und es wird angenommen, dass Sie die Übersicht über geroutete Ereignissegelesen haben. Um den Beispielen in diesem Artikel zu folgen, hilft es Ihnen, wenn Sie mit Extensible Application Markup Language (XAML) vertraut sind und wissen, wie Sie Windows Presentation Foundation (WPF)-Anwendungen schreiben.
Warum das schwache Ereignismuster implementieren?
Das Überwachen von Ereignissen kann zu Speicherlecks führen. Die übliche Technik zum Überwachen eines Ereignisses besteht darin, sprachspezifische Syntax zu verwenden, um einen Handler an ein Ereignis in einer Quelle anzufügen. Beispielsweise die C#-Anweisung source.SomeEvent += new SomeEventHandler(MyEventHandler)
oder die VB-Anweisung AddHandler source.SomeEvent, AddressOf MyEventHandler
. Diese Technik erstellt jedoch einen starken Verweis aus der Ereignisquelle auf den Ereignislistener. Sofern die Registrierung des Ereignishandlers nicht explizit aufgehoben ist, wird die Objektlebensdauer des Listeners durch die Objektlebensdauer der Quelle beeinflusst. Unter bestimmten Umständen möchten Sie möglicherweise, dass die Objektlebensdauer des Listeners durch andere Faktoren gesteuert wird, z. B. ob er derzeit zum visuellen Baum der Anwendung gehört. Wenn die Objektlebensdauer der Quelle über die lebensdauer des nützlichen Objekts des Listeners hinausgeht, wird der Listener länger als nötig lebendig gehalten. In diesem Fall führt der nicht zugeordnete Speicher zu einem Speicherleck.
Das schwache Ereignismuster wurde entwickelt, um das Speicherleckproblem zu lösen. Das schwache Ereignismuster kann verwendet werden, wenn ein Listener sich für ein Ereignis registrieren muss, aber der Listener weiß nicht explizit, wann die Registrierung aufgehoben werden soll. Das schwache Ereignismuster kann auch verwendet werden, wenn die Objektlebensdauer der Quelle die zweckmäßige Objektlebensdauer des Listeners überschreitet. In diesem Fall wird nützliche von Ihnen bestimmt. Das schwache Ereignismuster ermöglicht es dem Listener, das Ereignis zu registrieren und zu empfangen, ohne die Eigenschaften der Objektlebensdauer des Listeners auf irgendeine Weise zu beeinflussen. Tatsächlich bestimmt der implizite Verweis aus der Quelle nicht, ob der Listener für die Garbage Collection berechtigt ist. Der Verweis ist ein schwacher Verweis, daher die Benennung des schwachen Ereignismusters und der zugehörigen APIs. Der Listener kann von der Garbage Collection erfasst oder auf andere Weise zerstört werden, und die Quelle kann fortfahren, ohne nicht sammelbare Handler-Referenzen an ein jetzt zerstörtes Objekt beizubehalten.
Wer sollte das schwache Ereignismuster implementieren?
Das schwache Ereignismuster ist in erster Linie für Kontrollautoren relevant. Als Steuerelementsautor sind Sie weitgehend für das Verhalten und die Verwaltung Ihres Steuerelements sowie dessen Auswirkungen auf die Anwendungen, in die es eingefügt wird, verantwortlich. Dies schließt das Objektlebensdauerverhalten des Steuerelements ein, insbesondere die Behandlung des beschriebenen Speicherleckproblems.
Bestimmte Szenarien eignen sich inhärent für die Anwendung des Weak-Event-Patterns. Ein solches Szenario ist die Datenbindung. In der Datenbindung ist es üblich, dass das Quellobjekt unabhängig vom Listenerobjekt ist, das ein Ziel einer Bindung ist. Viele Aspekte der WPF-Datenbindung haben das schwache Ereignismuster bereits in der Art und Weise angewendet, wie die Ereignisse implementiert werden.
Wie man das schwache Ereignismuster implementiert
Es gibt vier Möglichkeiten, das schwache Ereignismuster zu implementieren, und jeder Ansatz verwendet einen anderen Ereignismanager. Wählen Sie den Ereignismanager aus, der ihrem Szenario am besten entspricht.
Vorhandener schwacher Ereignismanager:
Verwenden Sie eine vorhandene schwache Ereignis-Manager-Klasse, wenn das Ereignis, das Sie abonnieren möchten, über eine entsprechende WeakEventManagerverfügt. Eine Liste der schwachen Ereignismanager, die in WPF enthalten sind, finden Sie in der Vererbungshierarchie der Klasse
WeakEventManager
. Da die eingeschlossenen schwachen Ereignismanager begrenzt sind, müssen Sie wahrscheinlich einen der anderen Ansätze auswählen.Schwacher generischer Ereignismanager:
Verwenden Sie eine generische WeakEventManager<TEventSource,TEventArgs>, wenn eine vorhandene WeakEventManager nicht verfügbar ist und Sie nach der einfachsten Möglichkeit suchen, schwache Ereignisse zu implementieren. Die generische
WeakEventManager<TEventSource,TEventArgs>
ist jedoch weniger effizient als der vorhandene oder benutzerdefinierte schwache Ereignis-Manager, da sie Reflexion verwendet, um das Ereignis anhand seines Namens zu ermitteln. Außerdem ist der Code, der zum Registrieren des Ereignisses mit dem generischenWeakEventManager<TEventSource,TEventArgs>
erforderlich ist, ausführlicher als die Verwendung eines vorhandenen oder benutzerdefiniertenWeakEventManager
.Benutzerdefinierter schwacher Ereignis-Manager:
Wenn eine vorhandene
WeakEventManager
nicht verfügbar ist und Effizienz entscheidend ist, erstellen Sie eine benutzerdefinierte WeakEventManager. Obwohl eine benutzerdefinierteWeakEventManager
effizienter als eine generischeWeakEventManager
ist, erfordert sie, dass Sie vorab mehr Code schreiben müssen.Schwachereignismanager von Drittanbietern:
Verwenden Sie einen schwachen Ereignis-Manager eines Drittanbieters, wenn Sie Funktionen benötigen, die von den anderen Ansätzen nicht bereitgestellt werden. NuGet verfügt über einige schwache Ereignismanager. Viele WPF-Frameworks unterstützen auch das Muster.
In den folgenden Abschnitten wird beschrieben, wie Sie das schwache Ereignismuster mit verschiedenen Typen von Ereignis-Managern implementieren. Für die generischen und benutzerdefinierten Beispiele für schwache Ereignis-Manager weist das Ereignis, das abonniert werden soll, die folgenden Merkmale auf.
- Der Ereignisname ist
SomeEvent
. - Das Ereignis wird von der Klasse
SomeEventSource
ausgelöst. - Der Ereignishandler hat Typ
EventHandler<SomeEventArgs>
. - Das Ereignis übergibt einen Parameter vom Typ
SomeEventArgs
an die Ereignishandler.
Verwenden einer vorhandenen schwachen Ereignismanager-Klasse
Suchen Sie einen vorhandenen schwachen Ereignismanager. Eine Liste der schwachen Ereignismanager, die in WPF enthalten sind, finden Sie in der Vererbungshierarchie der Klasse WeakEventManager.
Verwenden Sie den neuen schwachen Ereignismanager anstelle des normalen Ereignis-Hookups.
Wenn Ihr Code beispielsweise das folgende Muster zum Abonnieren eines Ereignisses verwendet:
source.LostFocus += new RoutedEventHandler(Source_LostFocus);
AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
Ändern Sie es in das folgende Muster:
LostFocusEventManager.AddHandler(source, Source_LostFocus);
LostFocusEventManager.AddHandler( source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
Ähnlich, wenn Ihr Code das folgende Muster zum Abmelden von einem Ereignis verwendet:
source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
Ändern Sie es in das folgende Muster:
LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
LostFocusEventManager.RemoveHandler( source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
Verwenden Sie die generische schwache Ereignismanagerklasse
Verwenden Sie die generische WeakEventManager<TEventSource,TEventArgs>-Klasse anstelle der normalen Ereignisverknüpfung.
Wenn Sie WeakEventManager<TEventSource,TEventArgs>
zum Registrieren von Ereignislistenern verwenden, geben Sie die Ereignisquelle und den EventArgs-Typ als Typparameter für die Klasse an. Rufen Sie AddHandler auf, wie im folgenden Code dargestellt:
WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
Erstellen Sie eine benutzerdefinierte schwache Ereignismanagerklasse
Kopieren Sie die folgende Klassenvorlage in Ihr Projekt. Die folgende Klasse erbt von der WeakEventManager Klasse:
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
Benennen Sie
SomeEventWeakEventManager
,SomeEvent
,SomeEventSource
undSomeEventArgs
so um, dass sie ihrem Ereignisnamen entsprechen.Legen Sie die Zugriffsmodifizierer für die schwache Ereignis-Manager-Klasse fest, entsprechend der Zugänglichkeit des von ihr verwalteten Ereignisses.
Verwenden Sie den neuen schwachen Ereignismanager anstelle der normalen Ereigniszuordnung.
Wenn Ihr Code beispielsweise das folgende Muster zum Abonnieren eines Ereignisses verwendet:
source.SomeEvent += new EventHandler<SomeEventArgs>(Source_SomeEvent);
AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
Ändern Sie es in das folgende Muster:
SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
SomeEventWeakEventManager.AddHandler( source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
Wenn Ihr Code das folgende Muster zum Abmelden von einem Ereignis verwendet:
source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
Ändern Sie es in das folgende Muster:
SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
SomeEventWeakEventManager.RemoveHandler( source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
Siehe auch
- WeakEventManager
- IWeakEventListener
- Übersicht über Routingereignisse
- Die Übersicht über die Datenbindung
.NET Desktop feedback