Implementando IDisposable en una subclase cuando el padre también implementa IDisposable

Tengo una clase para padres e hijos que necesitan implementar IDisposable . ¿Dónde deberían entrar las llamadas virtual (y base.Dispose() ?)? Cuando simplemente anulo la llamada Dispose(bool disposing) , me parece realmente extraño afirmar que implemente IDisposable sin tener una función Dispose() explícita (simplemente utilizando la heredada), pero que tengo todo lo demás.

Lo que he estado haciendo (trivializado un poco):

 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); } } 

Cuando simplemente anulo la llamada Dispose (bool disposing), me parece realmente extraño afirmar que implemente IDisposable sin tener una función Dispose () explícita (simplemente utilizando la heredada), pero que tengo todo lo demás.

Esto es algo que no debería preocuparte.

Cuando subclasifica una clase IDisposable, la clase base ya está procesando toda la tubería de “Eliminar patrón”. No debería hacer nada más que anular el método protected Dispose(bool) y rastrear si ya se ha eliminado (para subir ObjectDisposedException correctamente).

Para obtener más información, consulte la publicación de mi blog sobre Subclassing de una clase IDisposable .


Además, a menudo, es una buena idea considerar encapsular la clase IDisposable en lugar de subclasificarla. Hay momentos en que la subclasificación de una clase IDisposable es apropiada, pero son algo raros. La encapsulación es a menudo una mejor alternativa.

¿Por qué complicar las cosas cuando no es necesario?

Ya que no encapsula ningún recurso no administrado, no necesita todo ese rebuscado con la finalización. Y, sus clases son internas, lo que sugiere que controle la jerarquía de herencia dentro de su propio ensamblaje.

Entonces, el enfoque directo sería:

 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(); } } 

Incluso cuando tienes recursos no administrados, diría que es mucho mejor que los encapsules en su propia clase desechable y los uses como si usaras cualquier otro desechable; tan directo como el código de arriba.

La idea de este patrón es que anule el método base.Dispose virtual, llamada base.Dispose . base.Dispose si es necesario. La clase base se ocupa del rest, llamando al método Dispose virtual (y de ahí la implementación correcta). La subclase no debería necesitar implementar IDisposable (es IDisposable través de la herencia)

La clase hija debe anular el Dispose virtual, deshacerse de cualquier disposición específica de la subclase y llamar a la superclase ‘Dispose’, que a su vez hará su propio trabajo.

EDIT: http://davybrion.com/blog/2008/06/disposing-of-the-dispositable-implementation/ es el patrón que sigo en tales casos. No específicamente la clase ‘Desechable’, sino la herencia y las anulaciones.

Siempre me refiero al estudio en profundidad de Joe Duffy sobre este patrón. Para mí, su versión es Gospel.

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

Lo primero que debe recordar es que un finalizador no es necesario la mayor parte del tiempo. Es para limpiar recursos no administrados en los que está manteniendo directamente recursos nativos, es decir, solo recursos que no tienen su propio finalizador.

Aquí hay un ejemplo para un par de subclase de clase 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 

Tenga en cuenta que la subclase tiene su propio miembro _isDisposed. También tenga en cuenta la verificación nula de los recursos ya que no desea ninguna excepción en estos bloques.

Luke