Share via


Modelli di eventi deboli

Nelle applicazioni è possibile che i gestori collegati alle origini eventi non vengano eliminati definitivamente in coordinamento con l'oggetto listener che ha collegato il gestore all'origine. Questa situazione può causare perdite di memoria. Windows Presentation Foundation (WPF) introduce un modello di progettazione che può essere usato per risolvere questo problema, fornendo una classe di gestione dedicata per eventi specifici e implementando un'interfaccia nei listener per tale evento. Questo modello di progettazione è noto come modello di evento debole.

Perché implementare il modello di evento debole?

L'ascolto degli eventi può causare perdite di memoria. La tecnica tipica per l'ascolto di un evento consiste nell'usare la sintassi specifica del linguaggio che collega un gestore a un evento in un'origine. Ad esempio, in C#, tale sintassi è: source.SomeEvent += new SomeEventHandler(MyEventHandler).

Questa tecnica crea un riferimento sicuro dall'origine evento al listener di eventi. In genere, l'associazione di un gestore eventi per un listener fa sì che il listener abbia una durata dell'oggetto influenzata dalla durata dell'oggetto dell'origine (a meno che il gestore eventi non venga rimosso in modo esplicito). In alcune circostanze, tuttavia, è possibile che la durata dell'oggetto del listener sia controllata da altri fattori, ad esempio se appartiene attualmente alla struttura ad albero visuale dell'applicazione e non dalla durata dell'origine. Ogni volta che la durata dell'oggetto di origine si estende oltre la durata dell'oggetto del listener, il modello di evento normale causa una perdita di memoria: il listener viene mantenuto attivo più a lungo del previsto.

Il modello di evento debole è progettato per risolvere questo problema di perdita di memoria. Il modello di evento debole può essere usato ogni volta che un listener deve registrarsi per un evento, ma il listener non sa in modo esplicito quando annullare la registrazione. Il modello di evento debole può essere usato anche ogni volta che la durata dell'oggetto dell'origine supera la durata utile dell'oggetto del listener. In questo caso, utile è determinato dall'utente. Il modello di evento debole consente al listener di registrarsi e ricevere l'evento senza influire sulle caratteristiche di durata dell'oggetto del listener in alcun modo. In effetti, il riferimento implicito dall'origine non determina se il listener è idoneo per Garbage Collection. Il riferimento è un riferimento debole, quindi la denominazione del modello di evento debole e delle API correlate. Il listener può essere sottoposto a Garbage Collection o distrutto in altro modo e l'origine può continuare senza conservare riferimenti al gestore noncollebili a un oggetto ora eliminato.

Chi deve implementare il modello di evento debole?

L'implementazione del modello di evento debole è interessante principalmente per gli autori di controlli. L'autore del controllo è in gran parte responsabile del comportamento e del contenimento del controllo e dell'impatto sulle applicazioni in cui viene inserito. Ciò include il comportamento della durata dell'oggetto di controllo, in particolare la gestione del problema di perdita di memoria descritto.

Alcuni scenari si prestano intrinsecamente all'applicazione del modello di evento debole. Uno di questi scenari è data binding. Nel data binding, è comune che l'oggetto di origine sia completamente indipendente dall'oggetto listener, che è una destinazione di un'associazione. Molti aspetti del data binding WPF hanno già il modello di evento debole applicato nel modo in cui vengono implementati gli eventi.

Come implementare il modello di evento debole

Esistono tre modi per implementare un modello di evento debole. La tabella seguente elenca i tre approcci e fornisce alcune indicazioni per l'uso di ogni approccio.

Approccio Quando implementare
Usare una classe di gestione eventi debole esistente Se l'evento a cui si vuole sottoscrivere ha un oggetto corrispondente WeakEventManager, usare il gestore eventi debole esistente. Per un elenco di gestori eventi deboli inclusi in WPF, vedere la gerarchia di ereditarietà nella WeakEventManager classe . Poiché i gestori eventi deboli inclusi sono limitati, è probabile che sia necessario scegliere uno degli altri approcci.
Usare una classe di gestione eventi debole generica Usare un generico WeakEventManager<TEventSource,TEventArgs> quando un esistente WeakEventManager non è disponibile, si vuole un modo semplice per implementare e non si è preoccupati per l'efficienza. Il generico WeakEventManager<TEventSource,TEventArgs> è meno efficiente di un gestore eventi debole esistente o personalizzato. Ad esempio, la classe generica esegue più reflection per individuare l'evento in base al nome dell'evento. Inoltre, il codice per registrare l'evento usando il generico WeakEventManager<TEventSource,TEventArgs> è più dettagliato rispetto all'uso di un oggetto esistente o personalizzato WeakEventManager.
Creare una classe di gestione eventi debole personalizzata Creare un oggetto personalizzato WeakEventManager quando un esistente WeakEventManager non è disponibile e si vuole ottenere la migliore efficienza. L'uso di un oggetto personalizzato WeakEventManager per sottoscrivere un evento sarà più efficiente, ma si comporta il costo della scrittura di più codice all'inizio.
Usare un gestore eventi debole di terze parti NuGet include diversi gestori eventi deboli e molti framework WPF supportano anche il modello.

Le sezioni seguenti descrivono come implementare il modello di evento debole. Ai fini di questa discussione, l'evento da sottoscrivere presenta le caratteristiche seguenti.

  • Il nome dell'evento è SomeEvent.

  • L'evento viene generato dalla EventSource classe .

  • Il gestore eventi ha il tipo ( SomeEventEventHandler o EventHandler<SomeEventEventArgs>).

  • L'evento passa un parametro di tipo SomeEventEventArgs ai gestori eventi.

Uso di una classe di Gestione eventi debole esistente

  1. Trovare un gestore eventi debole esistente.

    Per un elenco di gestori eventi deboli inclusi in WPF, vedere la gerarchia di ereditarietà nella WeakEventManager classe .

  2. Usare il nuovo gestore eventi debole invece del normale hook degli eventi.

    Ad esempio, se il codice usa il modello seguente per sottoscrivere un evento:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Impostarlo sul modello seguente:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Analogamente, se il codice usa il modello seguente per annullare la sottoscrizione a un evento:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    Impostarlo sul modello seguente:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Uso della classe Generic Weak Event Manager

  1. Usare la classe generica WeakEventManager<TEventSource,TEventArgs> anziché l'hook di eventi normale.

    Quando si usano WeakEventManager<TEventSource,TEventArgs> per registrare listener di eventi, specificare l'origine evento e EventArgs il tipo come parametri di tipo alla classe e chiamare AddHandler come illustrato nel codice seguente:

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

Creazione di una classe personalizzata Weak Event Manager

  1. Copiare il modello di classe seguente nel progetto.

    Questa classe eredita dalla WeakEventManager classe .

    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. Sostituire il SomeEventWeakEventManager nome con il proprio nome.

  3. Sostituire i tre nomi descritti in precedenza con i nomi corrispondenti per l'evento. (SomeEvent, EventSourcee SomeEventEventArgs)

  4. Impostare la visibilità (public/internal/private) della classe di gestione eventi debole sulla stessa visibilità dell'evento gestito.

  5. Usare il nuovo gestore eventi debole invece del normale hook degli eventi.

    Ad esempio, se il codice usa il modello seguente per sottoscrivere un evento:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Impostarlo sul modello seguente:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Analogamente, se il codice usa il modello seguente per annullare la sottoscrizione a un evento:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Impostarlo sul modello seguente:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Vedi anche