Finalizar / Eliminar patrón en C #

C # 2008

He estado trabajando en esto desde hace un tiempo, y todavía estoy confundido sobre algunos problemas. Mis preguntas están debajo

  1. Sé que solo necesita un finalizador si se deshace de los recursos no administrados. Sin embargo, si está usando recursos administrados que realizan llamadas a recursos no administrados, ¿todavía tendría que implementar un finalizador?

  2. Sin embargo, si desarrolla una clase que no utiliza ningún recurso no administrado, directa o indirectamente, ¿puede implementar IDisposable para que los clientes de su clase puedan usar la ‘statement de uso’?

    ¿Sería aceptable implementar el IDisposable solo para que los clientes de su clase puedan usar el enunciado using?

     using(myClass objClass = new myClass()) { // Do stuff here } 
  3. He desarrollado este sencillo código a continuación para demostrar el patrón Finalize / dispose:

     public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } } 

Pregunta sobre el código fuente:

  1. Aquí no he agregado el finalizador, y normalmente el GC llamará al finalizador, y el finalizador llamará al Dispose. Como no tengo el finalizador, ¿cuándo llamo al método Dispose? ¿Es el cliente de la clase el que tiene que llamarlo?

    Así que mi clase en el ejemplo se llama NoGateway y el cliente podría usar y deshacerse de la clase de esta manera:

     using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here } 

    ¿El método Dispose se invocará automáticamente cuando la ejecución llegue al final del bloque de uso o el cliente tendrá que llamar manualmente al método de eliminación? es decir

     NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it 
  2. Estoy usando la clase NoGateway en mi clase NoGateway . Debido a que el cliente web implementa la interfaz IDisposable, ¿significa esto que el cliente web usa indirectamente recursos no administrados? ¿Hay alguna regla difícil de seguir sobre esto? ¿Cómo sé que una clase usa recursos no administrados?

El patrón IDisposable recomendado está aquí . Cuando se progtwig una clase que usa IDisposable, generalmente debe usar dos patrones:

Al implementar una clase sellada que no utiliza recursos no administrados, simplemente implementa un método Dispose como con las implementaciones de interfaz normales:

 public sealed class A : IDisposable { public void Dispose() { // get rid of managed resources, call Dispose on member variables... } } 

Al implementar una clase no sellada, hazlo así:

 public class B : IDisposable { public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // get rid of managed resources } // get rid of unmanaged resources } // only if you use unmanaged resources directly in B //~B() //{ // Dispose(false); //} } 

Tenga en cuenta que no he declarado un finalizador en B ; solo debe implementar un finalizador si tiene recursos reales no administrados para eliminar. El CLR trata los objetos finalizables de forma diferente a los objetos no finalizables, incluso si se SuppressFinalize .

Por lo tanto, no debe declarar un finalizador a menos que sea necesario, pero le da un gancho a los herederos de su clase para llamar a su Dispose e implementar un finalizador si usan recursos no administrados directamente:

 public class C : B { private IntPtr m_Handle; protected override void Dispose(bool disposing) { if (disposing) { // get rid of managed resources } ReleaseHandle(m_Handle); base.Dispose(disposing); } ~C() { Dispose(false); } } 

Si no está utilizando recursos no administrados directamente ( SafeHandle y sus amigos no cuentan, ya que declaran sus propios finalizadores), entonces no implemente un finalizador, ya que el GC trata las clases finalizables de manera diferente, incluso si luego suprime el finalizador. . También tenga en cuenta que, aunque B no tiene un finalizador, todavía llama a SuppressFinalize para tratar correctamente con cualquier subclase que implemente un finalizador.

Cuando una clase implementa la interfaz IDisposable, significa que en algún lugar hay algunos recursos no administrados que deberían eliminarse cuando haya terminado de usar la clase. Los recursos reales están encapsulados dentro de las clases; no es necesario que los elimine explícitamente. Simplemente llamando a Dispose() o envolviendo la clase en a using(...) {} se asegurará de que cualquier recurso no administrado se elimine según sea necesario.

El patrón oficial para implementar IDisposable es difícil de entender. Creo que este es mejor :

 public class BetterDisposableClass : IDisposable { public void Dispose() { CleanUpManagedResources(); CleanUpNativeResources(); GC.SuppressFinalize(this); } protected virtual void CleanUpManagedResources() { // ... } protected virtual void CleanUpNativeResources() { // ... } ~BetterDisposableClass() { CleanUpNativeResources(); } } 

Una solución aún mejor es tener una regla que siempre tenga que crear una clase contenedora para cualquier recurso no administrado que necesite manejar:

 public class NativeDisposable : IDisposable { public void Dispose() { CleanUpNativeResource(); GC.SuppressFinalize(this); } protected virtual void CleanUpNativeResource() { // ... } ~NativeDisposable() { CleanUpNativeResource(); } } 

Con SafeHandle y sus derivados, estas clases deberían ser muy raras .

El resultado para las clases descartables que no se relacionan directamente con los recursos no administrados, incluso en presencia de herencia, es poderoso: ya no tienen que preocuparse por recursos no administrados . Serán simples de implementar y comprender:

 public class ManagedDisposable : IDisposable { public virtual void Dispose() { // dispose of managed resources } } 

Tenga en cuenta que cualquier implementación IDisposable debe seguir el patrón a continuación (en mi humilde opinión). Desarrollé este patrón basado en la información de varios “dioses” .NET excelentes. Las Pautas de diseño de .NET Framework (tenga en cuenta que MSDN no sigue esto por alguna razón). Las Pautas de diseño de .NET Framework fueron escritas por Krzysztof Cwalina (arquitecto de CLR en ese momento) y Brad Abrams (creo que el gerente de progtwig de CLR en ese momento) y Bill Wagner ([Effective C #] y [More Effective C #] (solo tome una busca estos en Amazon.com:

Tenga en cuenta que NUNCA debe implementar un Finalizador a menos que su clase contenga directamente (no herede) recursos no administrados. Una vez que implemente un Finalizer en una clase, incluso si nunca se llama, se garantiza que vivirá para una colección adicional. Se coloca automáticamente en la cola de finalización (que se ejecuta en un único hilo). Además, una nota muy importante … todo código ejecutado dentro de un Finalizador (si necesita implementar uno) DEBE ser seguro para subprocesos Y ¡a prueba de excepciones! MALO cosas pasarán de lo contrario … (es decir, comportamiento indeterminado y, en el caso de una excepción, un locking fatal de la aplicación irrecuperable).

El patrón que he reunido (y he escrito un fragmento de código) sigue:

 #region IDisposable implementation //TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable // Default initialization for a bool is 'false' private bool IsDisposed { get; set; } ///  /// Implementation of Dispose according to .NET Framework Design Guidelines. ///  /// Do not make this method virtual. /// A derived class should not be able to override this method. ///  public void Dispose() { Dispose( true ); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. // Always use SuppressFinalize() in case a subclass // of this type implements a finalizer. GC.SuppressFinalize( this ); } ///  /// Overloaded Implementation of Dispose. ///  ///  ///  /// Dispose(bool isDisposing) executes in two distinct scenarios. /// If  equals true, the method has been called directly /// or indirectly by a user's code. Managed and unmanaged resources /// can be disposed. /// If  equals false, the method has been called by the /// runtime from inside the finalizer and you should not reference /// other objects. Only unmanaged resources can be disposed. ///  protected virtual void Dispose( bool isDisposing ) { // TODO If you need thread safety, use a lock around these // operations, as well as in your methods that use the resource. try { if( !this.IsDisposed ) { if( isDisposing ) { // TODO Release all managed resources here $end$ } // TODO Release all unmanaged resources here // TODO explicitly set root references to null to expressly tell the GarbageCollector // that the resources have been disposed of and its ok to release the memory allocated for them. } } finally { // explicitly call the base class Dispose implementation base.Dispose( isDisposing ); this.IsDisposed = true; } } //TODO Uncomment this code if this class will contain members which are UNmanaged // ///// Finalizer for $className$ ///// This finalizer will run only if the Dispose method does not get called. ///// It gives your base class the opportunity to finalize. ///// DO NOT provide finalizers in types derived from this class. ///// All code executed within a Finalizer MUST be thread-safe! // ~$className$() // { // Dispose( false ); // } #endregion IDisposable implementation 

Aquí está el código para implementar IDisposable en una clase derivada. Tenga en cuenta que no necesita enumerar explícitamente la herencia de IDisposable en la definición de la clase derivada.

 public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass) protected override void Dispose( bool isDisposing ) { try { if ( !this.IsDisposed ) { if ( isDisposing ) { // Release all managed resources here } } } finally { // explicitly call the base class Dispose implementation base.Dispose( isDisposing ); } } 

He publicado esta implementación en mi blog en: Cómo implementar correctamente el patrón de eliminación

Estoy de acuerdo con pm100 (y debería haber dicho esto explícitamente en mi publicación anterior).

Nunca debe implementar IDisposable en una clase a menos que lo necesite. Para ser muy específico, hay alrededor de 5 veces en las que alguna vez necesitaría / debería implementar IDisposable:

  1. Su clase contiene explícitamente (es decir, no a través de herencia) cualquier recurso administrado que implemente IDisposable y debe limpiarse una vez que su clase ya no se use. Por ejemplo, si su clase contiene una instancia de Stream, DbCommand, DataTable, etc.

  2. Su clase contiene explícitamente cualquier recurso gestionado que implemente un método Close (), por ejemplo, IDataReader, IDbConnection, etc. Tenga en cuenta que algunas de estas clases implementan IDisposable teniendo el método Dispose () y Close ().

  3. Su clase contiene explícitamente un recurso no administrado, por ejemplo, un objeto COM, punteros (sí, puede usar punteros en C # administrado pero deben declararse en bloques “inseguros”, etc. En el caso de recursos no administrados, también debe asegurarse de llame a System.Runtime.InteropServices.Marshal.ReleaseComObject () en el RCW. Aunque el RCW es, en teoría, un contenedor administrado, todavía hay un conteo de referencias bajo las cubiertas.

  4. Si su clase se suscribe a eventos usando referencias fuertes. Necesita anular el registro / separarse de los eventos. ¡Siempre para asegurarse de que no sean nulos primero antes de intentar anular el registro / separación!

  5. Tu clase contiene cualquier combinación de los anteriores …

Una alternativa recomendada para trabajar con objetos COM y tener que usar Marshal.ReleaseComObject () es usar la clase System.Runtime.InteropServices.SafeHandle.

El BCL (Base Class Library Team) tiene una buena publicación en el blog aquí http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Una nota muy importante que debe hacer es que si está trabajando con WCF y limpiando recursos, SIEMPRE DEBERÍA evitar el bloque “usar”. Hay muchas publicaciones en el blog y algunas en MSDN sobre por qué esta es una mala idea. También publiqué sobre esto aquí – No use ‘using ()’ con un proxy WCF

Usar lambdas en lugar de IDisposable.

Nunca me he sentido entusiasmado con toda la idea de usar / IDisposable. El problema es que requiere que la persona que llama:

  • saber que deben usar IDisposable
  • recuerde usar ‘usar’.

Mi nuevo método preferido es utilizar un método de fábrica y una lambda en su lugar

Imagina que quiero hacer algo con un SqlConnection (algo que debería estar incluido en el uso). Clásicamente harías

 using (Var conn = Factory.MakeConnection()) { conn.Query(....); } 

Nueva manera

 Factory.DoWithConnection((conn)=> { conn.Query(...); } 

En el primer caso, la persona que llama simplemente no puede usar la syntax de uso. En el segundo caso, el usuario no tiene otra opción. No hay ningún método que cree un objeto SqlConnection, el llamador debe invocar DoWithConnection.

DoWithConnection se parece a esto

 void DoWithConnection(Action action) { using (var conn = MakeConnection()) { action(conn); } } 

MakeConnection ahora es privado

nadie respondió la pregunta sobre si debe implementar IDisposable aunque no lo necesite.

Respuesta corta: no

Respuesta larga:

Esto permitiría a un consumidor de su clase usar ‘usar’. La pregunta que haré es: ¿por qué lo harían? La mayoría de los desarrolladores no usarán ‘usar’ a menos que sepan que deben hacerlo, y cómo lo saben. Ya sea

  • sus obviuos los de experiencia (una clase socket por ejemplo)
  • está documentado
  • son cautelosos y pueden ver que la clase implementa IDisposable

Entonces, implementando IDisposable le está diciendo a los desarrolladores (al menos algunos) que esta clase termina algo que debe ser lanzado. Utilizarán ‘usar’, pero hay otros casos en los que no es posible usarlos (el scope del objeto no es local); y tendrán que empezar a preocuparse por la vida útil de los objetos en esos otros casos. Me preocuparía con seguridad. Pero esto no es necesario

Implementa Idisposable para permitirles usar using, pero no usarán a menos que se lo indiquen.

Entonces no lo hagas

  1. Si está utilizando otros objetos administrados que utilizan recursos no administrados, no es su responsabilidad asegurarse de que estén finalizados. Su responsabilidad es llamar a Dispose sobre esos objetos cuando se invoca a Dispose en su objeto, y se detiene allí.

  2. Si su clase no usa recursos escasos, no veo por qué haría que su clase implementara IDisposable. Solo deberías hacerlo si estás:

    • Sabrá que pronto tendrá recursos escasos en sus objetos, pero no ahora (y me refiero a que, como en “todavía estamos en desarrollo, estará aquí antes de que terminemos”, no como en “Creo que necesitaremos esto” “)
    • Usando recursos escasos
  3. Sí, el código que usa su código debe llamar al método Dispose de su objeto. Y sí, el código que usa tu objeto puede usarlo como lo has mostrado.

  4. (¿2 de nuevo?) Es probable que WebClient use recursos no administrados u otros recursos administrados que implementen IDisposable. La razón exacta, sin embargo, no es importante. Lo importante es que implementa IDisposable, por lo que depende de usted actuar según ese conocimiento al deshacerse del objeto cuando haya terminado con él, incluso si resulta que WebClient no utiliza ningún otro recurso en absoluto.

@Icey,

En realidad, tu respuesta es ligeramente incorrecta por 2 razones:

Primero,

 using(NoGateway objNoGateway = new NoGateway()) 

en realidad es equivalente a:

 try { NoGateway = new NoGateway(); } finally { if(NoGateway != null) { NoGateway.Dispose(); } } 

Esto puede sonar ridículo ya que el operador ‘nuevo’ nunca debe devolver ‘nulo’ a menos que tenga una excepción OutOfMemory. Pero considere los siguientes casos: 1. Llama a una FactoryClass que devuelve un recurso IDisposable o 2. Si tiene un tipo que puede o no heredar de IDisposable en función de su implementación, recuerde que he visto el patrón IDisposable implementado de forma incorrecta. veces en muchos clientes donde los desarrolladores solo agregan un método Dispose () sin heredar de IDisposable (malo, malo, malo). También podría tener el caso de que un recurso IDisposable sea devuelto por una propiedad o un método (otra vez malo, malo, malo, no ‘regale sus recursos IDisposables)

 using(IDisposable objNoGateway = new NoGateway() as IDisposable) { if (NoGateway != null) { ... 

Si el operador ‘como’ devuelve nulo (o propiedad o método que devuelve el recurso), y su código en el bloque ‘usar’ protege contra ‘nulo’, su código no explotará cuando intente llamar a Dispose en un objeto nulo debido a la comprobación nula “incorporada”.

La segunda razón por la cual su respuesta no es precisa es por la siguiente frase:

Se solicita un finalizador al GC destruyendo su objeto

En primer lugar, la finalización (así como el propio CG) no es determinista. THR CLR determina cuándo llamará a un finalizador. es decir, el desarrollador / código no tiene idea. Si el patrón IDisposable se implementa correctamente (como he publicado anteriormente) y se ha llamado a GC.SuppressFinalize (), NO se llamará al Finalizador. Esta es una de las razones principales para implementar correctamente el patrón correctamente. Dado que solo hay 1 subproceso de Finalizer por proceso gestionado, independientemente del número de procesadores lógicos, puede degradar fácilmente el rendimiento haciendo una copia de seguridad o incluso colgando el hilo de Finalizer olvidándose de llamar a GC.SuppressFinalize ().

He publicado una implementación correcta del Patrón de eliminación en mi blog: Cómo implementar correctamente el patrón de eliminación

Eliminar el patrón:

 public abstract class DisposableObject : IDisposable { public bool Disposed { get; private set;} public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~DisposableObject() { Dispose(false); } private void Dispose(bool disposing) { if (!Disposed) { if (disposing) { DisposeManagedResources(); } DisposeUnmanagedResources(); Disposed = true; } } protected virtual void DisposeManagedResources() { } protected virtual void DisposeUnmanagedResources() { } } 

Ejemplo de herencia:

 public class A : DisposableObject { public Component components_a { get; set; } private IntPtr handle_a; protected override void DisposeManagedResources() { try { Console.WriteLine("A_DisposeManagedResources"); components_a.Dispose(); components_a = null; } finally { base.DisposeManagedResources(); } } protected override void DisposeUnmanagedResources() { try { Console.WriteLine("A_DisposeUnmanagedResources"); CloseHandle(handle_a); handle_a = IntPtr.Zero; } finally { base.DisposeUnmanagedResources(); } } } public class B : A { public Component components_b { get; set; } private IntPtr handle_b; protected override void DisposeManagedResources() { try { Console.WriteLine("B_DisposeManagedResources"); components_b.Dispose(); components_b = null; } finally { base.DisposeManagedResources(); } } protected override void DisposeUnmanagedResources() { try { Console.WriteLine("B_DisposeUnmanagedResources"); CloseHandle(handle_b); handle_b = IntPtr.Zero; } finally { base.DisposeUnmanagedResources(); } } } 
 using(NoGateway objNoGateway = new NoGateway()) 

es equivalente a

 try { NoGateway = new NoGateway(); } finally { NoGateway.Dispose(); } 

Se solicita un finalizador al GC destruyendo su objeto. Esto puede ser en un momento totalmente diferente que cuando abandonas tu método. El Dispose of IDisposable se invoca inmediatamente después de salir del bloque de uso. Por lo tanto, el patrón generalmente se usa para liberar recursos inmediatamente después de que ya no los necesite.

1) WebClient es un tipo administrado, por lo que no necesita un finalizador. El finalizador es necesario en el caso de que los usuarios no desechen () su clase NoGateway y el tipo nativo (que no es recolectado por el GC) debe ser limpiado después. En este caso, si el usuario no llama a Dispose (), el WebClient contenido será eliminado por GC justo después de que NoGateway lo haga.

2) Indirectamente, sí, pero no debería preocuparse por eso. Su código es correcto como está y no puede evitar que sus usuarios se olviden de tirar () muy fácilmente.

Patrón de msdn

 public class BaseResource: IDisposable { private IntPtr handle; private Component Components; private bool disposed = false; public BaseResource() { } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if(!this.disposed) { if(disposing) { Components.Dispose(); } CloseHandle(handle); handle = IntPtr.Zero; } disposed = true; } ~BaseResource() { Dispose(false); } public void DoSomething() { if(this.disposed) { throw new ObjectDisposedException(); } } } public class MyResourceWrapper: BaseResource { private ManagedResource addedManaged; private NativeResource addedNative; private bool disposed = false; public MyResourceWrapper() { } protected override void Dispose(bool disposing) { if(!this.disposed) { try { if(disposing) { addedManaged.Dispose(); } CloseHandle(addedNative); this.disposed = true; } finally { base.Dispose(disposing); } } } } 

Por lo que sé, es muy recomendable NO usar el Finalizer / Destructor:

 public ~MyClass() { //dont use this } 

Principalmente, esto se debe a que no se sabe cuándo o SI se llamará. El método de eliminación es mucho mejor, especialmente si lo usamos o desechamos directamente.

usar es bueno. usarlo 🙂