Schwache Ereignismuster
In Anwendungen ist es möglich, dass Handler, die an Ereignisquellen angefügt sind, nicht in Abstimmung 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, indem eine dedizierte Managerklasse für bestimmte Ereignisse bereitgestellt und eine Schnittstelle für Listener für dieses Ereignis implementiert wird. Dieses Entwurfsmuster wird als Weak Event Patternbezeichnet.
Warum sollte man das schwache Ereignismuster implementieren?
Das Überwachen von Ereignissen kann zu Speicherlecks führen. Die typische Technik, ein Ereignis abzuhören, besteht darin, die sprachspezifische Syntax zu verwenden, die einen Handler an ein Ereignis an einer Quelle anfügt. In C# lautet diese Syntax beispielsweise: source.SomeEvent += new SomeEventHandler(MyEventHandler)
.
Diese Technik erstellt einen starken Verweis von der Ereignisquelle auf den Ereignis-Listener. Normalerweise führt das Anhängen eines Ereignis-Handlers an einen Listener dazu, dass der Listener eine Objektlebensdauer hat, die durch die Objektlebensdauer der Quelle beeinflusst wird (es sei denn, der Ereignis-Handler wird ausdrücklich entfernt). Unter bestimmten Umständen möchten Sie jedoch, dass die Objektlebensdauer des Listeners von anderen Faktoren gesteuert wird, z. B. ob sie derzeit zur visuellen Struktur der Anwendung gehört, und nicht durch die Lebensdauer der Quelle. Wenn die Lebensdauer des Quellobjekts über die Objektlebensdauer des Listeners hinausgeht, führt das normale Ereignismuster zu einem Speicherleck: Der Listener bleibt länger aktiv, als beabsichtigt.
Das Muster für schwache Ereignisse wurde entwickelt, um das Problem des Speicherlecks 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 nützliche Objektlebensdauer des Listeners übersteigt. (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, was die Bezeichnung des schwachen Ereignismusters und der zugehörigen APIs erklärt. Der Listener kann der Speicherbereinigung zugeführt oder auf andere Weise zerstört werden, und die Quelle kann ohne nicht sammelbare Handler-Referenzen auf ein nun zerstörtes Objekt fortfahren.
Wer sollte das schwache Ereignismuster implementieren?
Die Implementierung des schwachen Ereignismusters ist in erster Linie für Entwickler von Steuerelementen interessant. Als Autor von Steuerelementen sind Sie weitgehend für das Verhalten und die Verwaltung Ihres Steuerelements sowie für die Auswirkungen auf Anwendungen verantwortlich, in denen es eingefügt wird. Dies umfasst das Verhalten der Lebensdauer von Steuerobjekten, insbesondere die Behandlung des in der Beschreibung genannten Speicherverlustproblems.
Bestimmte Szenarien eignen sich für die Anwendung des schwachen Ereignismusters. Ein solches Szenario ist die Datenbindung. In der Datenbindung ist es üblich, dass das Quellobjekt vollständig unabhängig vom Listenerobjekt ist, das ein Ziel einer Bindung ist. Viele Aspekte der WPF-Datenbindung verwenden bereits das schwache Ereignismuster bei der Implementierung der Ereignisse.
Wie man das schwache Ereignismuster implementiert
Es gibt drei Möglichkeiten, schwaches Ereignismuster zu implementieren. In der folgenden Tabelle sind die drei Ansätze aufgeführt. Es werden einige Hinweise gegeben, wann jeder Ansatz angewendet werden sollte.
Ansatz | Wann muss ich implementieren? |
---|---|
Verwenden einer vorhandenen schwachen Ereignismanagerklasse | Wenn das Ereignis, das Sie abonnieren möchten, über eine entsprechende WeakEventManagerverfügt, verwenden Sie den vorhandenen schwachen Ereignismanager. Eine Liste der schwachen Ereignismanager, die in WPF enthalten sind, finden Sie in der Vererbungshierarchie der WeakEventManager-Klasse. Da die eingeschlossenen schwachen Ereignismanager begrenzt sind, müssen Sie wahrscheinlich einen der anderen Ansätze auswählen. |
Verwenden Sie eine generische schwache Ereignismanager-Klasse | Verwenden Sie eine generische WeakEventManager<TEventSource,TEventArgs>, wenn eine vorhandene WeakEventManager nicht verfügbar ist, wenn Sie eine einfache Implementierungsmöglichkeit suchen und wenn Ihnen die Effizienz nicht so wichtig ist. Die generische WeakEventManager<TEventSource,TEventArgs> ist weniger effizient als ein vorhandener oder benutzerdefinierter schwacher Ereignismanager. Die generische Klasse führt z. B. mehr Reflexion durch, um das Ereignis anhand des Namens des Ereignisses zu ermitteln. Außerdem ist der Code zum Registrieren des Ereignisses mithilfe des generischen WeakEventManager<TEventSource,TEventArgs> ausführlicher als der Code, der ein vorhandenes oder benutzerdefiniertes WeakEventManagerverwendet. |
Erstellen einer benutzerdefinierten schwachen Ereignis-Manager-Klasse | Erstellen Sie eine benutzerdefinierte WeakEventManager, wenn eine vorhandene WeakEventManager nicht verfügbar ist und Sie die höchste Effizienz erzielen möchten. Die Verwendung einer benutzerdefinierten WeakEventManager zum Abonnieren eines Ereignisses ist effizienter, allerdings tragen Sie die Kosten, am Anfang mehr Code zu schreiben. |
Verwenden Sie einen schwachen Ereignis-Manager eines Drittanbieters. | NuGet verfügt über mehrere schwache Ereignismanager und viele WPF-Frameworks unterstützen auch das Muster. |
In den folgenden Abschnitten wird beschrieben, wie das Muster für schwache Ereignisse implementiert wird. Für diese Diskussion hat das Ereignis, das abonniert werden soll, die folgenden Merkmale.
Der Ereignisname ist
SomeEvent
.Das Ereignis wird von der Klasse
EventSource
ausgelöst.Der Typ des Ereignishandlers ist:
SomeEventEventHandler
(oderEventHandler<SomeEventEventArgs>
).Das Ereignis übergibt einen Parameter vom Typ
SomeEventEventArgs
an die Ereignishandler.
Verwenden einer vorhandenen schwachen Ereignis-Manager-Klasse
Suchen Sie einen vorhandenen schwachen Ereignismanager.
Eine Liste der schwachen Ereignismanager, die in WPF enthalten sind, finden Sie in der Vererbungshierarchie in der WeakEventManager Klasse.
Verwenden Sie den neuen schwachen Ereignismanager anstelle des normalen Ereignis-Hookups.
Wenn Ihr Code beispielsweise das folgende Muster zum Abonnieren eines Ereignisses verwendet:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
Ändern Sie es in das folgende Muster:
SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
Ähnlich, wenn Ihr Code das folgende Muster verwendet, um sich von einem Ereignis abzumelden:
source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
Ändern Sie es in das folgende Muster:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
Verwenden der generischen Weak Event Manager-Klasse
Verwenden Sie die generische WeakEventManager<TEventSource,TEventArgs>-Klasse anstelle der normalen Ereignisbindung.
Wenn Sie WeakEventManager<TEventSource,TEventArgs> zum Registrieren von Ereignislistenern verwenden, geben Sie die Ereignisquelle und EventArgs Typ als Typparameter für die Klasse an und rufen AddHandler auf, wie im folgenden Code gezeigt:
WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
Erstellen einer benutzerdefinierten Klasse für Weak Event Manager
Kopieren Sie die folgende Klassenvorlage in Ihr Projekt.
Diese Klasse erbt von der Klasse 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); } }
Ersetzen Sie den Namen
SomeEventWeakEventManager
durch Ihren eigenen Namen.Ersetzen Sie die drei zuvor beschriebenen Namen durch die entsprechenden Namen für Ihr Ereignis. (
SomeEvent
,EventSource
undSomeEventEventArgs
)Legen Sie die Sichtbarkeit (öffentlich/intern/privat) der schwachen Ereignis-Manager-Klasse auf die gleiche Sichtbarkeit wie das von ihr verwaltete Ereignis fest.
Verwenden Sie den neuen schwachen Ereignismanager anstelle der normalen Ereignisverknüpfung.
Wenn Ihr Code beispielsweise das folgende Muster zum Abonnieren eines Ereignisses verwendet:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
Ändern Sie es in das folgende Muster:
SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
Wenn Ihr Code das folgende Muster zum Abmelden von einem Ereignis verwendet:
source.SomeEvent -= new SomeEventEventHandler(OnSome);
Ändern Sie es in das folgende Muster:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
Siehe auch
.NET Desktop feedback