Implementazione IDisposable su una sottoclass quando il genitore implementa anche IDisposable

Ho una class genitore e figlio che entrambi devono implementare IDisposable . Dove dovrebbero entrare in gioco le chiamate virtual (e base.Dispose() ?)? Quando eseguo semplicemente l’override della chiamata Dispose(bool disposing) , mi sembra davvero strano affermare che implemento IDisposable senza avere una esplicita funzione Dispose() (solo utilizzando quella ereditata), ma che ha tutto il resto.

Quello che stavo facendo (banalizzato un po ‘):

 internal class FooBase : IDisposable { Socket baseSocket; private void SendNormalShutdown() { } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { SendNormalShutdown(); } baseSocket.Close(); } } ~FooBase() { Dispose(false); } } internal class Foo : FooBase, IDisposable { Socket extraSocket; private bool _disposed = false; protected override void Dispose(bool disposing) { if (!_disposed) { extraSocket.Close(); } base.Dispose(disposing); } ~Foo() { Dispose(false); } } 

Quando eseguo semplicemente l’override della chiamata Dispose (bool disposing), mi sembra davvero strano affermare che implemento IDisposable senza avere una esplicita funzione Dispose () (solo utilizzando quella ereditata), ma che ha tutto il resto.

Questo è qualcosa che non dovresti preoccuparti.

Quando si sottoclass una class IDisposable, tutte le tubature “Dispose pattern” vengono già gestite dalla class base. Non dovresti davvero fare nulla, ma sovrascrivere il metodo protected Dispose(bool) , e tenere traccia se sei stato già eliminato (per sollevare correttamente ObjectDisposedException .)

Per i dettagli, vedere il post sul mio blog su sottoclassi da una class IDisposable .


Inoltre, spesso, è una buona idea considerare l’incapsulamento della class IDisposable invece della sua sottoclass. Ci sono momentjs in cui la sottoclass di una class IDisposable è appropriata, ma sono piuttosto rari. L’incapsulamento è spesso un’alternativa migliore.

Perché complicare le cose quando non è necessario?

Dal momento che non incapsulare alcuna risorsa non gestita, non è necessario tutto ciò che riguarda la finalizzazione. E le tue classi sono interne, il che suggerisce che tu controlli la gerarchia dell’ereditarietà all’interno del tuo assembly.

Quindi, l’approccio diretto sarebbe:

 internal class FooBase : IDisposable { Socket baseSocket; private void SendNormalShutdown() { // ... } private bool _disposed = false; public virtual void Dispose() { if (!_disposed) { SendNormalShutdown(); baseSocket.Close(); _disposed = true; } } } internal class Foo : FooBase { Socket extraSocket; private bool _disposed = false; public override void Dispose() { if (!_disposed) { extraSocket.Close(); _disposed = true; } base.Dispose(); } } 

Anche quando hai risorse non gestite, direi che stai molto meglio incapsulandole nella loro class usa e getta e usandole come se tu usassi qualsiasi altro usa e getta; come facilmente come il codice sopra.

L’idea di questo modello è che si sostituisce il metodo Dispose virtuale, chiamando base.Dispose se necessario. La class base si occupa del resto, chiamando il metodo Dispose virtuale (e quindi l’implementazione corretta). La sottoclass non dovrebbe essere necessario implementare anche IDisposableIDisposable via ereditarietà)

La class figlio deve ignorare il Dispose virtuale, eseguire qualsiasi disposizione specifica per la sottoclass e chiamare la superclass ‘Dispose, che a sua volta farà il proprio lavoro.

EDIT: http://davybrion.com/blog/2008/06/disposing-of-the-idisposable-implementation/ è lo schema che seguo in questi casi. Non la class ‘Disposable’ in particolare, ma l’ereditarietà e le sostituzioni.

Mi rivolgo sempre allo studio molto approfondito di Joe Duffy su questo modello. Per me, la sua versione è Gospel.

http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/

La prima cosa da ricordare è che un finalizzatore non è necessario la maggior parte del tempo. Serve a svuotare risorse non gestite dove si trovano direttamente risorse native, ovvero solo risorse che non dispongono di un proprio finalizzatore.

Ecco un esempio per una coppia di sottoclassi di class base.

 // Base class #region IDisposable Members private bool _isDisposed; public void Dispose() { this.Dispose(true); // GC.SuppressFinalize(this); // Call after Dispose; only use if there is a finalizer. } protected virtual void Dispose(bool isDisposing) { if (!_isDisposed) { if (isDisposing) { // Clear down managed resources. if (this.Database != null) this.Database.Dispose(); } _isDisposed = true; } } #endregion // Subclass #region IDisposable Members private bool _isDisposed; protected override void Dispose(bool isDisposing) { if (!_isDisposed) { if (isDisposing) { // Clear down managed resources. if (this.Resource != null) this.Resource.Dispose(); } _isDisposed = true; } base.Dispose(isDisposing); } #endregion 

Si noti che la sottoclass ha il proprio membro _isDisposed. Notare anche il controllo nullo sulle risorse poiché non si desidera alcuna eccezione in questi blocchi.

Luca