¿Está bien tener un método virtual Dispose () siempre que todas las clases en la jerarquía solo usen recursos administrados?

Sé que la pauta general para implementar el patrón de disposición advierte contra la implementación de un Dispose() virtual. Sin embargo, la mayoría de las veces solo trabajamos con recursos administrados dentro de una clase, por lo que el patrón de disposición total parece una exageración, es decir, no necesitamos un finalizador. En tales casos, ¿está bien tener un Dispose() virtual en la clase base?

Considera este simple ejemplo:

 abstract class Base : IDisposable { private bool disposed = false; public SecureString Password { get; set; } // SecureString implements IDisposable. public virtual void Dispose() { if (disposed) return; if (Password != null) Password.Dispose(); disposed = true; } } class Sub : Base { private bool disposed = false; public NetworkStream NStream { get; set; } // NetworkStream implements IDisposable. public override void Dispose() { if (disposed) return; if (NStream != null) NStream.Dispose(); disposed = true; base.Dispose(); } } 

Encuentro esto más legible que un patrón de disposición completo. Entiendo que este ejemplo sería problemático si introdujéramos un recurso no administrado en la clase Base . Pero supongamos que esto no sucederá. ¿Es el ejemplo anterior válido / seguro / robusto, o plantea algún problema? ¿Debo seguir el patrón de disposición estándar en toda regla, incluso si no se utilizan recursos no administrados? ¿O es que el enfoque anterior está perfectamente bien en esta situación, que según mi experiencia es mucho más común que tratar con recursos no administrados?

Me he rendido en el patrón IDisposable en todos los casos en los que no estoy tratando con recursos no administrados.

¡No veo ninguna razón por la que no pueda renunciar al patrón y hacer que Dispose virtual si puede asegurarse de que las clases derivadas no presenten recursos no administrados!

(Y aquí radica un problema, porque no puede imponer esto. No tiene control sobre los campos que agregará una clase derivada, por lo que no hay seguridad absoluta con un Dispose virtual. Pero incluso si usó el patrón completo, un la clase derivada podría hacer las cosas mal, por lo que siempre hay algo de confianza involucrada en que los subtipos se adhieren a ciertas reglas.)

Primero, en los casos en que solo tratamos con objetos administrados, tener un finalizador no tendría sentido: si se llama a Dispose desde el finalizador ( Dispose(disposing: false) acuerdo con el patrón completo), es posible que no podamos acceder / desreferencia con seguridad a otros Objetos gestionados de tipo de referencia porque ya podrían haber desaparecido. Solo se puede acceder de forma segura a los objetos de tipo de valor accesibles desde el objeto que se está finalizando.

Si el finalizador no tiene sentido, también es inútil tener el indicador de disposing , que se utiliza para distinguir si se llamó a Dispose explícitamente o si fue llamado por el finalizador.

Tampoco es necesario hacer GC.SuppressFinalize si no hay finalizador.

Ni siquiera estoy seguro de si es imperativo que Dispose no arroje excepciones en un escenario solo de gestión. AFAIK esta regla existe principalmente para proteger el subproceso finalizador. (Ver el comentario de @ usr más abajo ).

Como puede ver, una vez que el finalizador se va, gran parte del patrón desechable clásico ya no es necesario.

Entiendo que este ejemplo sería problemático si introdujéramos un recurso no administrado en la clase Base. Pero supongamos que esto no sucederá. ¿Es el ejemplo anterior válido / seguro / robusto, o plantea algún problema? ¿Debo seguir el patrón de disposición estándar en toda regla, incluso si no se utilizan recursos no administrados?

Aunque su clase base utiliza recursos administrados exclusivamente (y esto no cambiará en el futuro), no puede garantizar que una clase derivada no usará recursos no administrados. Entonces, considere implementar el Patrón de Disposición Básico en su clase base (una clase con un Dispose(bool) virtual Dispose(bool) pero sin finalizador) incluso si siempre llama el método Dispose(bool) con true .

Cuando algún día se derivará una nueva subclase de su Base , que utiliza recursos no administrados, es posible que su finalizador quiera llamar a Dispose(bool) con false . Por supuesto, podría introducir un nuevo Dispose(bool) en la clase derivada:

 public class SubUnmanaged : Base { IntPtr someNativeHandle; IDisposable someManagedResource; public override sealed void Dispose() { base.Dispose(); Dispose(true); GC.SuppressFinalize(this); } ~SubUnmanaged(); { base.Dispose(); Dispose(false); } protected virtual void Dispose(bool disposing) { if (someNativeHandle != IntPtr.Zero) Free(someNativeHandle); if (disposing) someManagedResource.Dispose(); } } 

Pero creo que esto es un poco desagradable. Al sellar el método virtual original de Dispose() , puede evitar que otros herederos se confundan debido a los múltiples métodos virtuales de Dispose , y puede llamar a la base común. base.Dispose() tanto del finalizador como del Dispose() cerrado. Debe hacer la misma solución en cada subclase, que utiliza recursos no administrados y se deriva de Base o una subclase totalmente administrada. Pero esto ya está lejos del patrón, lo que siempre hace las cosas difíciles.