Schwache Ereignismuster

In Anwendungen ist es möglich, dass Handler, die an Ereignisquellen angefügt sind, nicht in Koordination 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, das zum Beheben dieses Problems verwendet 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 schwaches Ereignismuster bezeichnet.

Warum sollen wir das schwache Ereignismuster implementieren?

Das Überwachen von Ereignissen kann zu Arbeitsspeicherverlusten führen. Die typische Technik zum Überwachen eines Ereignisses besteht darin, die sprachspezifische Syntax zu verwenden, die einen Handler an ein Ereignis in 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 Ereignislistener. Normalerweise bewirkt das Anfügen eines Ereignishandlers für einen Listener, dass der Listener eine Objektlebensdauer hat, die von der Objektlebensdauer der Quelle beeinflusst wird (es sei denn, der Ereignishandler wird explizit entfernt). Unter bestimmten Umständen kann jedoch die Objektlebensdauer des Listeners von anderen Faktoren gesteuert werden, z. B. ob sie zur visuellen Struktur der Anwendung gehört, und nicht von der Lebensdauer der Quelle. Wenn sich die Lebensdauer des Quellobjekts über die Objektlebensdauer des Listeners erstreckt, führt das normale Ereignismuster zu einem Arbeitsspeicherverlust: Der Listener wird länger als beabsichtigt gehalten.

Das schwache Ereignismuster wurde entwickelt, um dieses Problem mit dem Arbeitsspeicherverlust zu lösen. Das schwache Ereignismuster kann verwendet werden, wenn sich ein Listener für ein Ereignis registrieren muss, aber der Listener nicht explizit weiß, wann die Registrierung aufgehoben werden soll. Das schwache Ereignismuster kann auch verwendet werden, wenn die Objektlebensdauer der Quelle die Lebensdauer als nützliches Objekt des Listeners überschreitet. (In diesem Fall wird nützlich von Ihnen bestimmt.) Das schwache Ereignismuster ermöglicht es dem Listener, sich für das Ereignis zu registrieren und es zu empfangen, ohne die Objektlebensdauer des Listeners in irgendeiner 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, also die Benennung des schwachen Ereignismusters und der zugehörigen APIs. Der Listener kann gesammelt oder anderweitig zerstört werden, und die Quelle kann fortgesetzt werden, ohne dass nicht erfasste Handlerverweise auf ein jetzt zerstörtes Objekt aufbewahrt werden.

Wer sollte das schwache Ereignismuster implementieren?

Die Implementierung des schwachen Ereignismusters ist in erster Linie für Steuerelementautoren interessant. Als Steuerelementautor sind Sie weitgehend verantwortlich für das Verhalten und die Eindämmung Ihres Steuerelements und die Auswirkungen auf Anwendungen, in denen sie eingefügt wird. Dies schließt das Verhalten der Objektlebensdauer des Steuerelements ein, insbesondere die Behandlung des beschriebenen Speicherleckproblems.

Bestimmte Szenarien verleihen sich inhärent an die Anwendung des schwachen Ereignismusters. Ein solches Szenario ist die Datenbindung. Bei der Datenbindung ist es üblich, dass das Quellobjekt vollständig unabhängig vom Listenerobjekt ist, das Ziel einer Bindung ist. Viele Aspekte der WPF-Datenbindung haben bereits das schwache Ereignismuster angewendet, in das die Ereignisse implementiert werden.

Implementieren des schwachen Ereignismusters

Es gibt drei Möglichkeiten zum Implementieren eines schwachen Ereignismusters. In der folgenden Tabelle sind die drei Ansätze aufgeführt, und es werden einige Anleitungen für die Verwendung der einzelnen Methoden bereitgestellt.

Vorgehensweise Gründe für die Implementierung
Verwenden einer vorhandenen schwachen Ereignismanagerklasse Wenn das Ereignis, das Sie abonnieren möchten, über einen entsprechenden WeakEventManager verfügt, verwenden Sie den vorhandenen schwachen Ereignismanager. Eine Liste der schwachen Ereignismanager, die in WPF enthalten sind, finden Sie in der Vererbungshierarchie in der WeakEventManager-Klasse. Da die eingeschlossenen schwachen Ereignismanager eingeschränkt sind, müssen Sie wahrscheinlich einen der anderen Ansätze auswählen.
Verwenden einer generischen schwachen Ereignismanagerklasse Verwenden Sie ein generisches, WeakEventManager<TEventSource,TEventArgs> wenn ein vorhandenes WeakEventManager nicht verfügbar ist, sie möchten eine einfache Möglichkeit zur Implementierung, und Sie sind nicht besorgt über Effizienz. Das generische WeakEventManager<TEventSource,TEventArgs> ist weniger effizient als ein vorhandener oder benutzerdefinierter schwacher Ereignis-Manager. Die generische Klasse verwendet beispielsweise mehr Reflexion, um das Ereignis zu ermitteln, das dem Namen des Ereignisses zugewiesen ist. Außerdem ist der Code zum Registrieren des Ereignisses mithilfe des generischen WeakEventManager<TEventSource,TEventArgs>-Ereignisses ausführlicher als die Verwendung einer vorhandenen oder benutzerdefinierten WeakEventManager.
Erstellen einer benutzerdefinierten schwachen Ereignismanagerklasse Erstellen Sie einen benutzerdefinierten WeakEventManager-Wert, wenn eine vorhandene WeakEventManager nicht verfügbar ist und Sie die beste Effizienz wünschen. Die Verwendung einer benutzerdefinierten WeakEventManager zum Abonnieren eines Ereignisses ist effizienter, aber Sie müssen zu Beginn mehr Code schreiben.
Verwenden eines schwachen Ereignismanagers von Drittanbietern NuGet verfügt über mehrere Manager für schwache Ereignismuster, und viele WPF-Frameworks unterstützen das Muster ebenfalls.

In den folgenden Abschnitten wird beschrieben, wie das schwache Ereignismuster implementiert wird. Für diese Diskussion hat das zu abonnierende Ereignis die folgenden Merkmale.

  • Der Ereignisname ist SomeEvent.

  • Das Ereignis wird von der EventSource-Klasse ausgelöst.

  • Der Ereignishandler hat Typ: SomeEventEventHandler (oder EventHandler<SomeEventEventArgs>).

  • Das Ereignis übergibt einen Parameter vom Typ SomeEventEventArgs an die Ereignishandler.

Verwenden einer vorhandenen schwachen Ereignismanagerklasse

  1. 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.

  2. Verwenden Sie den neuen schwachen Ereignismanager anstelle des normalen Ereignis-Hookups.

    Wenn Ihr Code beispielsweise das folgende Muster verwendet, um ein Ereignis zu abonnieren:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Ändern Sie es in das folgende Muster:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    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, schwachen Ereignismanagerklasse

  1. Verwenden Sie die generische WeakEventManager<TEventSource,TEventArgs>-Klasse anstelle des normalen Ereignis-Hookups.

    Wenn Sie WeakEventManager<TEventSource,TEventArgs>-Ereignislistener registrieren, geben Sie die Ereignisquelle und den Typ als Typparameter für die Klasse und EventArgs den Aufruf AddHandler an, wie im folgenden Code gezeigt:

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

Erstellen einer benutzerdefinierten schwachen Ereignismanagerklasse

  1. Kopieren Sie die folgende Klassenvorlage in Ihr Projekt.

    Diese 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(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. Ersetzen Sie den SomeEventWeakEventManager-Namen durch Ihren eigenen Namen.

  3. Ersetzen Sie die zuvor beschriebenen drei Namen durch die entsprechenden Namen für Ihr Ereignis. (SomeEvent, EventSource und SomeEventEventArgs)

  4. Legen Sie die Sichtbarkeit (öffentlich/intern/privat) der schwachen Ereignis-Manager-Klasse auf die gleiche Sichtbarkeit fest wie das Ereignis, das sie verwaltet.

  5. Verwenden Sie den neuen schwachen Ereignismanager anstelle des normalen Ereignis-Hookups.

    Wenn Ihr Code beispielsweise das folgende Muster verwendet, um ein Ereignis zu abonnieren:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Ändern Sie es in das folgende Muster:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Wenn Ihr Code das folgende Muster verwendet, um das Abonnement eines Ereignisses kündigen zu können:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Ändern Sie es in das folgende Muster:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Weitere Informationen