Freigeben über


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 (oder EventHandler<SomeEventEventArgs>).

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

Verwenden einer vorhandenen schwachen Ereignis-Manager-Klasse

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

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

  1. 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);
        }
    }
    
  2. Ersetzen Sie den Namen SomeEventWeakEventManager durch Ihren eigenen Namen.

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

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

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