Utilizzo di IDisposable per annullare l’iscrizione degli eventi

Ho una class che gestisce gli eventi da un controllo WinForms. Sulla base di ciò che l’utente sta facendo, sto differenziando un’istanza della class e creando una nuova istanza per gestire lo stesso evento. Ho bisogno di annullare l’iscrizione alla vecchia istanza dall’evento in primo luogo – abbastanza facile. Mi piacerebbe farlo in un modo non proprietario, se ansible, e sembra che questo sia un lavoro per IDisposable. Tuttavia, la maggior parte della documentazione raccomanda IDisposable solo quando si utilizzano risorse non gestite, che non si applicano qui.

Se implemento IDisposable e unsubscribe dall’evento in Dispose (), sto pervertendo la sua intenzione? Dovrei invece fornire una funzione Unsubscribe () e chiamarla?


Modifica: Ecco un codice fittizio che mostra quello che sto facendo (usando IDisposable). La mia effettiva implementazione è legata ad alcuni binding di dati proprietari (lunga storia).

class EventListener : IDisposable { private TextBox m_textBox; public EventListener(TextBox textBox) { m_textBox = textBox; textBox.TextChanged += new EventHandler(textBox_TextChanged); } void textBox_TextChanged(object sender, EventArgs e) { // do something } public void Dispose() { m_textBox.TextChanged -= new EventHandler(textBox_TextChanged); } } class MyClass { EventListener m_eventListener = null; TextBox m_textBox = new TextBox(); void SetEventListener() { if (m_eventListener != null) m_eventListener.Dispose(); m_eventListener = new EventListener(m_textBox); } } 

Nel codice effettivo, la class “EventListener” è più coinvolta e ogni istanza è significativa in modo univoco. Li uso in una raccolta e li creo / li distruggo mentre l’utente fa clic.


Conclusione

Sto accettando la risposta di gbjbaanb , almeno per ora. Sento che il vantaggio di usare un’interfaccia familiare supera qualsiasi ansible inconveniente nell’usarlo in assenza di codice non gestito (come potrebbe sapere un utente di questo object?).

Se qualcuno non è d’accordo, si prega di postare / commentare / modificare. Se un argomento migliore può essere fatto contro IDisposable, allora cambierò la risposta accettata.

Sì, provaci. Sebbene alcune persone credano che l’IDisposable sia implementato solo per risorse non gestite, non è questo il caso: le risorse non gestite sono la vittoria più grande e la ragione più ovvia per implementarle. Penso che abbia acquisito questa idea perché la gente non poteva pensare ad alcun altro motivo per usarla. Non è come un finalizzatore che è un problema di prestazioni e non è facile da gestire per il GC.

Inserisci il codice di ordinamento nel tuo metodo di smaltimento. Sarà più chiaro, più pulito e molto più probabile che prevenga perdite di memoria e una vista maledettamente più facile da usare rispetto al tentativo di ricordare di annullare i tuoi riferimenti.

L’intenzione di IDisposable è di far funzionare meglio il tuo codice senza che tu debba fare molto lavoro manuale. Usa il suo potere a tuo favore e supera le “sciocchezze” artificiali di “intenzione progettuale”.

Ricordo che è stato abbastanza difficile persuadere Microsoft dell’utilità della finalizzazione deterministica quando .NET è uscito per la prima volta – abbiamo vinto la battaglia e li abbiamo persuasi ad aggiungerlo (anche se era solo un modello di design in quel momento), usalo!

Il mio voto personale sarebbe di avere un metodo di cancellazione per rimuovere la class dagli eventi. IDisposable è un modello destinato al rilascio deterministico di risorse non gestite. In questo caso non gestisci risorse non gestite e pertanto non dovresti implementare IDisposable.

IDisposable può essere utilizzato per gestire gli abbonamenti agli eventi ma probabilmente non dovrebbe. Per un esempio ti indico a WPF. Questa è una biblioteca piena di eventi e gestori di eventi. Eppure virtualmente nessuna class in strumenti WPF IDisposable. Lo prenderei come un’indicazione che gli eventi dovrebbero essere gestiti in un altro modo.

Una cosa che mi infastidisce dell’utilizzo di un pattern IDisposable per l’annullamento dell’iscrizione dagli eventi è il problema di Finalizzazione.

Dispose() in IDisposable dovrebbe essere chiamata dallo sviluppatore, tuttavia, se non viene chiamata dallo sviluppatore, è inteso che il GC chiamerà questa funzione (secondo lo schema standard IDisposable , almeno). Nel tuo caso, tuttavia, se non chiami Dispose nessun altro lo farà. L’evento rimane e il riferimento forte impedisce a GC di chiamare il finalizzatore.

Il semplice fatto che Dispose () non venga chiamato automaticamente da GC mi sembra abbastanza per non utilizzare IDisposable in questo caso. Forse richiede una nuova interfaccia specifica dell’applicazione che dice che questo tipo di object deve avere una funzione di pulizia chiamata per essere eliminata da GC.

Penso che l’usa e getta sia per tutto ciò che GC non è in grado di gestire automaticamente e i riferimenti agli eventi contano nel mio libro. Ecco una class di supporto che ho trovato.

 public class DisposableEvent : IDisposable { EventHandler> Target { get; set; } public T Args { get; set; } bool fired = false; public DisposableEvent(EventHandler> target) { Target = target; Target += new EventHandler>(subscriber); } public bool Wait(int howLongSeconds) { DateTime start = DateTime.Now; while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds) { Thread.Sleep(100); } return fired; } void subscriber(object sender, EventArgs e) { Args = e.Value; fired = true; } public void Dispose() { Target -= subscriber; Target = null; } } 

che ti permette di scrivere questo codice:

 Class1 class1 = new Class1(); using (var x = new DisposableEvent(class1.Test)) { if (x.Wait(30)) { var result = x.Args; } } 

Un effetto collaterale, non è necessario utilizzare la parola chiave evento sui tuoi eventi, dal momento che impedisce di passarli come parametro al costruttore helper, tuttavia, che sembra non avere effetti negativi.

Da tutto ciò che ho letto sui dispositivi usa e getta direi che sono stati principalmente inventati per risolvere un problema: liberare le risorse del sistema non gestito in modo tempestivo. Ma ancora tutti gli esempi che ho trovato non sono solo focalizzati sull’argomento delle risorse non gestite, ma hanno anche un’altra proprietà in comune: Dispose è chiamato solo per velocizzare un processo che altrimenti si sarebbe verificato in seguito automaticamente (GC -> finalizzatore -> smaltire)

Tuttavia, la chiamata di un metodo di smaltimento che annulla l’annullamento di un evento non si verificherebbe automaticamente, anche se si aggiungesse un finalizzatore che chiamerebbe il proprio sistema di smaltimento. (almeno non finché esiste l’object proprietario dell’object – e se si chiamerebbe non si trarrebbe beneficio dalla disiscrizione, dato che anche l’object proprietario dell’object sarebbe andato via comunque)

Quindi la differenza principale è che gli eventi creano in qualche modo un grafo di oggetti che non può essere raccolto, poiché l’object di gestione degli eventi diventa improvvisamente referenziato del servizio che si voleva solo referenziare / utilizzare. All’improvviso sei costretto a chiamare Dispose – non è ansible lo smaltimento automatico . Dispose otterrebbe quindi un significato sottile diverso da quello che si trova in tutti gli esempi in cui una chiamata Dispose – in una teoria sporca;) – non è necessaria, poiché verrebbe chiamata automaticamente (in qualche momento) …

Comunque. Dato che il pattern monouso è già abbastanza complicato (si tratta di finalizzatori difficili da ottenere e molte linee guida / contratti) e, cosa più importante nella maggior parte dei punti, non ha nulla a che fare con l’argomento di riferimento del ritorno dell’evento, direi che sarebbe è più facile ottenere quello separato nelle nostre teste semplicemente non usando quella metafora per qualcosa che potrebbe essere chiamato “unroot from object graph” / “stop” / “turn off”.

Ciò che vogliamo ottenere è disabilitare / interrompere alcuni comportamenti (annullando l’iscrizione a un evento). Sarebbe bello avere un’interfaccia standard come IStoppable con un metodo Stop (), che per contratto è focalizzato su

  • ottenere l’object (+ tutti i suoi stessi fermi) disconnesso dagli eventi di qualsiasi object che non ha creato da solo
  • in modo che non venga più chiamato in modalità di evento implicito (quindi può essere percepito come interrotto)
  • possono essere raccolti non appena tutti i riferimenti tradizionali su quell’object sono spariti

Chiamiamo l’unico metodo di interfaccia che esegue la disiscrizione “Stop ()”. Sapresti che l’object fermato è in uno stato accettabile, ma viene fermato. Forse una semplice proprietà “fermato” sarebbe anche un piacere avere.

Avrebbe anche senso avere un’interfaccia “IRestartable” che eredita da IStoppable e ha anche un metodo “Restart ()” se vuoi semplicemente mettere in pausa un certo comportamento che sarà sicuramente necessario in futuro, o per memorizzare un eliminato object del modello in una cronologia per il successivo annullamento del ripristino.

Dopo tutto quello che scrivo devo confessare di aver appena visto un esempio di IDisposable da qualche parte qui sopra: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx Ma comunque finchè ottenere ogni dettaglio e la motivazione originale di IObservable direi che non è il miglior esempio di caso d’uso

  • poiché di nuovo è un sistema piuttosto complicato attorno ad esso e abbiamo solo un piccolo problema qui
  • e potrebbe essere che una delle motivazioni di quello che l’intero nuovo sistema è quello di sbarazzarsi degli eventi in primo luogo, il che si tradurrebbe in una sorta di overflow dello stack riguardante la domanda originale

Ma sembra che siano sulla giusta strada. Ad ogni modo: avrebbero dovuto usare la mia interfaccia “IStoppable”;) poiché credo fermamente che ci sia una differenza in

  • Smaltisci: ” dovresti chiamare quel metodo o qualcosa potrebbe fuoriuscire se il GC capita in ritardo” ….

e

  • Stop: “devi chiamare questo metodo per fermare un determinato comportamento

IDisposable è fermamente sulle risorse, e la fonte di abbastanza problemi per non infangare ulteriormente le acque penso.

Sto votando per un metodo di annullamento sottoscrizioni anche sulla tua interfaccia.

Un’opzione potrebbe non essere quella di annullare l’iscrizione – solo per cambiare il significato dell’abbonamento. Se il gestore eventi può essere reso abbastanza intelligente da sapere cosa si intende fare in base al contesto, non è necessario annullare l’iscrizione in primo luogo.

Potrebbe non essere una buona idea nel tuo caso particolare – non penso che abbiamo davvero abbastanza informazioni – ma vale la pena considerare.

Un’altra opzione sarebbe quella di utilizzare delegati deboli o qualcosa di simile agli eventi deboli di WPF , invece di dover annullare l’iscrizione in modo esplicito.

PS [OT] Considero la decisione di fornire ai delegati forti l’unico errore di progettazione più costoso della piattaforma .NET.

No, non stai impedendo l’intenzione di IDisposable. IDisposable è inteso come un modo universale per garantire che quando hai finito di usare un object, puoi ripulire in modo proattivo tutto ciò che è legato a quell’object. Non deve essere solo risorse non gestite, ma può anche includere risorse gestite. E un abbonamento all’evento è solo un’altra risorsa gestita!

Uno scenario simile che si verifica frequentemente nella pratica è che implementerai IDisposable sul tuo tipo, puramente per assicurarti di poter chiamare Dispose () su un altro object gestito. Neanche questa è una perversione, è solo una gestione delle risorse ordinata!