metodo method implement how example correctly c# .net idisposable finalizer

method - metodo dispose() c#



Finalizar/Eliminar patrĂ³n en C# (13)

C # 2008

He estado trabajando en esto por un tiempo y todavía estoy confundido acerca de algunos problemas. Mis preguntas están abajo

  1. Sé que solo necesita un finalizador si está desechando recursos no administrados. Sin embargo, si está utilizando recursos administrados que realizan llamadas a recursos no administrados, ¿todavía necesitaría implementar un finalizador?

  2. Sin embargo, si desarrolla una clase que no usa ningún recurso no administrado, directa o indirectamente, ¿puede implementar el IDisposable para que los clientes de su clase puedan usar la ''declaración de uso''?

    ¿Sería aceptable implementar el IDisposable solo para que los clientes de su clase puedan usar la declaración de uso?

    using(myClass objClass = new myClass()) { // Do stuff here }

  3. He desarrollado este sencillo código a continuación para demostrar el patrón de Finalizar / Eliminar:

    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 finalizador será llamado por el GC, y el finalizador llamará al Dispose. Como no tengo el finalizador, ¿cuándo llamo al método de Disposición? ¿Es el cliente de la clase que tiene que llamarlo?

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

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

    ¿Se llamaría automáticamente al método de Disposición cuando la ejecución llegue al final del bloque de uso, o el cliente tiene que llamar manualmente al método de disposició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 utiliza indirectamente recursos no administrados? ¿Hay una regla difícil y rápida a seguir sobre esto? ¿Cómo sé que una clase usa recursos no administrados?


  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 en esos objetos cuando Dispose se llame a su objeto y se detenga allí.

  2. Si su clase no utiliza ningún recurso escaso, no veo por qué haría que su implemento de clase fuera IDisponible. Solo debes hacerlo si eres:

    • Sepa que pronto tendrá escasos recursos en sus objetos, pero no ahora (y quiero decir que como en "todavía estamos desarrollando, 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 usar using 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 y, por lo tanto, le corresponde a usted actuar sobre ese conocimiento al desechar el objeto cuando haya terminado con él, incluso si resulta que WebClient no utiliza ningún otro recurso.


1) WebClient es un tipo administrado, por lo que no necesita un finalizador. El finalizador es necesario en el caso de que sus usuarios no desechen () de su clase NoGateway y el tipo nativo (que no sea recopilado por el GC) deba limpiarse después. En este caso, si el usuario no llama a Dispose (), el cliente eliminará el WebClient contenido justo después de que lo haga NoGateway.

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


@Icey,

En realidad, su 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 parecer 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 FactoryClass que devuelve un recurso IDisposable o 2. Si tiene un tipo que puede o no puede heredar de IDisposable dependiendo de su implementación, recuerde que he visto el patrón IDisposable implementado incorrectamente muchos veces en muchos clientes donde los desarrolladores simplemente agregan un método Dispose () sin heredar de IDisposable (mal, mal, mal). También podría tener el caso de que un recurso IDisponible se devuelva desde una propiedad o método (nuevamente malo, malo, malo - no regale sus recursos IDisponibles)

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 ''usando'' protege contra ''nulo'', su código no explotará cuando intente llamar a Dispose en un objeto nulo debido a El ''incorporado'' cheque nulo.

La segunda razón por la que su respuesta no es precisa es debido al siguiente mensaje:

Se llama a un finalizador al CG que destruye tu objeto.

Primero, la finalización (así como la propia GC) no es determinista. El 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 lo he publicado anteriormente) y se ha llamado a GC.SuppressFinalize (), NO se llamará al finalizador. Esta es una de las grandes razones para implementar correctamente el patrón correctamente. Ya que solo hay 1 subproceso Finalizer por proceso administrado, independientemente de la cantidad de procesadores lógicos, puede degradar fácilmente el rendimiento al realizar una copia de seguridad o incluso colgar el subproceso Finalizer olvidando llamar a GC.SuppressFinalize ()

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


El patrón IDisponible recomendado está here . Al programar una clase que usa IDisposable, generalmente debe usar dos patrones:

Cuando implementa una clase sellada que no usa recursos no administrados, simplemente implementa un método Dispose como en las implementaciones de interfaz normales:

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

Cuando implementes una clase sin sellar, 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 no administrados reales para disponer. El CLR trata los objetos finalizables de forma diferente a los objetos no finalizables, incluso si se llama a SuppressFinalize .

Por lo tanto, no debe declarar un finalizador a menos que tenga que hacerlo, pero le da a los herederos de su clase un gancho 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 los 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 las subclases que implementan un finalizador.

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


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

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 de que siempre debe 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 desechables que no tratan directamente con recursos no administrados, incluso en presencia de herencia, es poderoso: ya no necesitan preocuparse por los recursos no administrados . Serán simples de implementar y entender:

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


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


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

Nunca debe implementar IDisposable en una clase a menos que lo necesite. Para ser muy específicos, hay aproximadamente 5 veces cuando alguna vez necesitarías / deberías implementar IDisposable:

  1. Su clase contiene explícitamente (es decir, no a través de la herencia) los recursos administrados que implementan IDisposable y deben 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 administrado que implemente un método Close (), por ejemplo, IDataReader, IDbConnection, etc. Tenga en cuenta que algunas de estas clases implementan IDisposable al tener Dispose () así como un método 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 ''no seguros'', 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, una envoltura administrada, todavía hay un conteo de referencias en las coberturas.

  4. Si su clase se suscribe a eventos usando referencias fuertes. Necesita darse de baja / desvincularse de los eventos. ¡Siempre asegúrese de que estos no sean nulos antes de intentar desregistrarlos / separarlos!

  5. Su clase contiene cualquier combinación de lo anterior ...

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 de blog al respecto aquí 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 debe evitar SIEMPRE el bloque ''usar''. Hay muchas publicaciones en el blog y algunas en MSDN sobre por qué es una mala idea. También he publicado sobre esto aquí - No use ''usar ()'' con un proxy WCF


Por lo que sé, es altamente recomendable NO usar el Finalizador / Destructor:

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

Sobre todo, esto se debe a no saber cuándo o SI se llamará. El método de desechar es mucho mejor, especialmente si usamos o desechamos directamente.

el uso es bueno utilízalo :)


Tenga en cuenta que cualquier implementación IDisposable debe seguir el siguiente patrón (IMHO). Desarrollé este patrón basado en la información de varios "dioses" excelentes de .NET, las Directrices de diseño del marco de .NET (tenga en cuenta que MSDN no sigue esto por alguna razón). Las Directrices de diseño de .NET Framework fueron escritas por Krzysztof Cwalina (CLR Architect en ese momento) y Brad Abrams (creo que el Administrador del programa CLR en ese momento) y Bill Wagner ([Effective C #] y [More Effect C #] (solo tome un busque estos en Amazon.com:

Tenga en cuenta que NUNCA debe implementar un Finalizer a menos que su clase contenga directamente (no herede) recursos no administrados. Una vez que implementas un Finalizer en una clase, incluso si nunca se llama, está garantizado que vive para una colección adicional. Se coloca automáticamente en la cola de finalización (que se ejecuta en un solo hilo). Además, una nota muy importante ... ¡todo el código ejecutado dentro de un Finalizer (en caso de que necesite implementar uno) DEBE ser seguro para subprocesos Y excepcionalmente seguro! Las cosas MALAS sucederán de lo contrario ... (es decir, un comportamiento indeterminado y, en el caso de una excepción, un fallo fatal de la aplicación).

El patrón que he juntado (y escrito un fragmento de código para) 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; } /// <summary> /// Implementation of Dispose according to .NET Framework Design Guidelines. /// </summary> /// <remarks>Do not make this method virtual. /// A derived class should not be able to override this method. /// </remarks> 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 ); } /// <summary> /// Overloaded Implementation of Dispose. /// </summary> /// <param name="isDisposing"></param> /// <remarks> /// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios. /// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly /// or indirectly by a user''s code. Managed and unmanaged resources /// can be disposed.</item> /// <item>If <paramref name="isDisposing"/> 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.</item></list></para> /// </remarks> 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 // ///// <summary>Finalizer for $className$</summary> ///// <remarks>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!</remarks> // ~$className$() // { // Dispose( false ); // } #endregion IDisposable implementation

Aquí está el código para implementar IDisposable en una clase derivada. Tenga en cuenta que no es necesario 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 disposición


Usando lambdas en lugar de IDisposable.

Nunca me ha entusiasmado la idea general utilizando / 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 usar un método de fábrica y un lambda en su lugar

Imagina que quiero hacer algo con un SqlConnection (algo que debería estar envuelto en un 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 sintaxis 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 ve así

void DoWithConnection(Action<SqlConnection> 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 que un consumidor de su clase use ''usar''. La pregunta que me gustaría hacer 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

  • es obvio el de la experiencia (una clase de socket por ejemplo)
  • esta documentado
  • son cautelosos y pueden ver que la clase implementa IDisposable

Entonces, al implementar IDisposable, le está diciendo a los desarrolladores (al menos a algunos) que esta clase envuelve algo que debe ser publicado. Utilizarán "usar", pero hay otros casos en los que no es posible usar (el alcance del objeto no es local); y tendrán que comenzar a preocuparse por la vida útil de los objetos en esos otros casos, me preocuparía con seguridad. Pero esto no es necesario

Usted implementa Idisposable para permitirles usar, pero no lo usarán a menos que usted lo indique.

Así que no lo hagas


Patrón desde 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); } } } }


using(NoGateway objNoGateway = new NoGateway())

es equivalente a

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

Se llama a un finalizador al CG que destruye tu objeto. Esto puede ser en un momento totalmente diferente que cuando abandonas tu método. El desecho de IDisposable se llama inmediatamente después de que abandone el bloque de uso. Por lo tanto, el patrón suele ser usar el uso de recursos libres inmediatamente después de que ya no los necesite.