Modelli di eventi deboli
Nelle applicazioni è possibile che i gestori collegati alle origini eventi non vengano distrutti insieme all'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 al linguaggio che collega un gestore a un evento di una sorgente. Ad esempio, in C#, tale sintassi è: source.SomeEvent += new SomeEventHandler(MyEventHandler)
.
Questa tecnica crea un riferimento forte dalla sorgente dell'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 del ciclo di vita dell'oggetto listener sia controllata da altri fattori, ad esempio dal fatto se appartiene attualmente all'albero visivo dell'applicazione, e non dalla durata di vita della sorgente. Ogni volta che il ciclo di vita dell'oggetto di origine si estende oltre quello 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 utilizzato anche ogni volta che la vita dell'oggetto sorgente supera la vita utile dell'oggetto ascoltatore. In questo caso, l'utilità di è determinata dall'utente. Il modello di evento debole consente all'ascoltatore di registrarsi e ricevere l'evento senza influire in alcun modo sulle caratteristiche di durata dell'oggetto dell'ascoltatore. In effetti, il riferimento implicito dall'origine non determina se il listener è idoneo per Garbage Collection. Il riferimento è un riferimento debole, da cui deriva 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 a funzionare senza conservare riferimenti al gestore non raccoglibili di un oggetto ormai distrutto.
Chi deve implementare il modello di evento debole?
L'implementazione del modello di evento debole è interessante principalmente per gli autori di controlli. Come autore di un controllo, sei in gran parte responsabile del suo comportamento e del suo contenimento, oltre che dell'impatto sulle applicazioni in cui viene inserito. Questo include il comportamento del ciclo di vita dell'oggetto di controllo, in particolare la gestione del problema della perdita di memoria descritto.
Alcuni scenari si prestano intrinsecamente all'applicazione del modello di evento debole. Uno di questi scenari è il data binding. Nel data binding, è comune che l'oggetto di origine sia completamente indipendente dall'oggetto listener, che funge da destinazione per un binding. 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.
Avvicinarsi | Quando implementare |
---|---|
Usare una classe esistente di gestore di eventi debole | Se l'evento a cui ci si vuole iscrivere ha un corrispondente WeakEventManager, utilizzare il gestore eventi debole esistente. Per un elenco di gestori eventi deboli inclusi in WPF, vedere la gerarchia di ereditarietà nella classe WeakEventManager. Poiché i gestori eventi deboli inclusi sono limitati, è probabile che sia necessario scegliere uno degli altri approcci. |
Usare una classe generica per la gestione debole degli eventi | Usare un WeakEventManager<TEventSource,TEventArgs> generico quando un WeakEventManager esistente non è disponibile, si vuole un modo semplice per implementare facilmente, e non ci si preoccupa dell'efficienza. Il WeakEventManager<TEventSource,TEventArgs> generico è meno efficiente di un gestore eventi debole esistente o personalizzato. Ad esempio, la classe generica esegue un uso maggiore della riflessione per individuare l'evento utilizzando il nome dell'evento. Inoltre, il codice per registrare l'evento usando il WeakEventManager<TEventSource,TEventArgs> generico è più verboso rispetto all'uso di un WeakEventManagerpersonalizzato o esistente. |
Creare una classe di gestione eventi debole personalizzata | Creare un WeakEventManager personalizzato quando un WeakEventManager esistente non è disponibile e si vuole ottenere la migliore efficienza. L'uso di un WeakEventManager personalizzato 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 ha diversi gestori di eventi deboli e molti framework WPF supportano inoltre 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 classe
EventSource
.Il gestore eventi ha tipo:
SomeEventEventHandler
(oEventHandler<SomeEventEventArgs>
).L'evento passa un parametro di tipo
SomeEventEventArgs
ai gestori eventi.
Uso di una classe esistente di Weak Event Manager
Trovare un gestore eventi debole esistente.
Per un elenco di gestori eventi deboli inclusi in WPF, vedere la gerarchia di ereditarietà nella classe WeakEventManager.
Usare il nuovo gestore degli eventi debole invece del collegamento normale degli eventi.
Ad esempio, se il codice usa il modello seguente per sottoscrivere un evento:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
Cambialo al 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);
Modificalo al modello seguente:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
Uso della classe Generic Weak Event Manager
Usare la classe generica WeakEventManager<TEventSource,TEventArgs> invece del normale collegamento dell'evento.
Quando si usa WeakEventManager<TEventSource,TEventArgs> per registrare i listener di eventi, fornire l'origine dell'evento e il tipo di EventArgs 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
Copiare il modello di classe seguente nel progetto.
Questa classe eredita dalla classe 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); } }
Sostituisci il nome
SomeEventWeakEventManager
con il tuo nome.Sostituire i tre nomi descritti in precedenza con i nomi corrispondenti per l'evento. (
SomeEvent
,EventSource
eSomeEventEventArgs
)Impostare la visibilità (public/internal/private) della classe di gestione eventi debole allo stesso livello di visibilità dell'evento che gestisce.
Usare il nuovo gestore eventi debole invece del normale collegamento degli eventi.
Ad esempio, se il codice usa il modello seguente per sottoscrivere un evento:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
Impostarlo secondo il 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);
Cambia al seguente modello:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
Vedere anche
.NET Desktop feedback