Uso del método Finalizar / Eliminar en C #

381

C # 2008

He estado trabajando en esto por un tiempo, y todavía estoy confundido sobre el uso de los métodos de finalizar y desechar en el código. Mis preguntas están abajo:

  1. Sé que solo necesitamos un finalizador mientras desechamos recursos no administrados. Sin embargo, si hay recursos administrados que hacen llamadas a recursos no administrados, ¿aún necesitaría implementar un finalizador?

  2. Sin embargo, si desarrollo una clase que no usa ningún recurso no administrado, directa o indirectamente, ¿debo implementar el IDisposablepara permitir que los clientes de esa clase usen la 'declaración de uso'?

    ¿Sería factible implementar IDisposable solo para permitir que los clientes de su clase utilicen la instrucción using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. He desarrollado este simple código a continuación para demostrar el uso 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 GC llamará al finalizador, y el finalizador llamará a Dispose. Como no tengo el finalizador, ¿cuándo llamo al método Dispose? ¿Es el cliente de la clase quien debe llamarlo?

    Entonces, 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   
    }

    ¿Se llamará automáticamente al método Dispose cuando la ejecución llegue al final del bloque de uso, o el cliente tiene que llamar manualmente al método dispose? es decir

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Estoy usando la WebClientclase en mi NoGatewayclase. Debido a que WebClientimplementa la IDisposableinterfaz, ¿significa esto que WebClientindirectamente usa recursos no administrados? ¿Existe una regla difícil y rápida para seguir esto? ¿Cómo sé que una clase usa recursos no administrados?

ant2009
fuente
1
¿Es este patrón de diseño complicado realmente necesario para resolver este problema de transmisión de recursos?
2013

Respuestas:

422

El patrón IDisposable recomendado está aquí . Al programar 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...
    }
}

Cuando implemente una clase sin sellar, hágalo 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 manera diferente a los objetos no finalizables, incluso si SuppressFinalizese llama.

Por lo tanto, no debe declarar un finalizador a menos que sea necesario, pero le da a los herederos de su clase un gancho para que lo llamen Disposee implementen 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 ( SafeHandley los amigos no cuentan, ya que declaran sus propios finalizadores), 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 Bno tiene un finalizador, todavía llama SuppressFinalizepara tratar correctamente 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 deben eliminarse cuando haya terminado de usar la clase. Los recursos reales están encapsulados dentro de las clases; no necesita eliminarlos explícitamente. Simplemente llamando Dispose()o ajustando la clase en una using(...) {}se asegurará de que los recursos no administrados se eliminen según sea necesario.

thecoop
fuente
26
Estoy de acuerdo con thecoop. Tenga en cuenta que no necesita un finalizador si solo se trata de recursos administrados (de hecho, NO debe intentar acceder a los objetos administrados desde su finalizador (que no sea "esto"), porque no hay un orden garantizado en el que el GC limpiará objetos. Además, si está utilizando .Net 2.0 o superior, puede (y debe) usar SafeHandles para envolver los identificadores no administrados. Safehandles reduce en gran medida su necesidad de escribir finalizadores para sus clases administradas. Blogs.msdn. com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch
55
Creo que es mejor realizar una llamada a MessageBox.Show ("Error", + GetType (). Nombre + "no desechado") en el finalizador, ya que el objeto desechable SIEMPRE debe desecharse, y si no lo hace, es Es mejor ser alertado del hecho lo antes posible.
erikkallen
95
@erikkallen, ¿es una broma? :)
Alex Norcliffe
2
ya que se necesita un esfuerzo informático adicional en el CLR para realizar un seguimiento de las clases con finalizadores activos. - La implementación de un finalizador hace que esto suceda. Llamar a GC.SuppressFinalize significa que el Finalizador no debe ser llamado por el tiempo de ejecución. Todavía va Gen2 independientemente. No agregue un finalizador si no está tratando con recursos administrados. Los modificadores de clase sellados o no sellados son irrelevantes para ese punto.
Ritch Melton
3
@Ritch: cita? Eso no es necesariamente algo malo; si está implementando IDisposable, lo más probable es que se quede por un tiempo de todos modos. Estás ahorrando al CLR el esfuerzo de tener que copiarlo de Gen0 -> Gen1 -> Gen2
thecoop el
123

El patrón oficial para implementar IDisposablees 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 de que siempre debe crear una clase de contenedor 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 SafeHandley 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 fáciles de implementar y comprender:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}
Jordão
fuente
@ Kyle: ¡Gracias! Me gusta también :-) No es una continuación de lo aquí .
Jordão
44
Aunque una cosa que quiero señalar es que no evita que se llame por segunda vez.
HuseyinUslu
55
@HuseyinUslu: esta es solo la esencia del patrón. Ciertamente puede agregar una disposedbandera y marcar en consecuencia.
Jordão
2
@didibus: es una simple cuestión de agregar una disposedbandera, verificarla antes de eliminarla y configurarla después de eliminarla. Mira aquí la idea. También debe verificar la bandera antes de cualquier método de la clase. ¿Tiene sentido? ¿Es complicado?
Jordão
1
+1 para "Una solución aún mejor es tener una regla que siempre tenga que crear una clase de contenedor para cualquier recurso no administrado que necesite manejar" . Me topé con esto en un complemento para VLC y lo he estado usando desde entonces. Ahorra tantos dolores de cabeza ...
Franz B.
37

Tenga en cuenta que cualquier implementación IDisposable debe seguir el siguiente patrón (en mi humilde opinión). Desarrollé este patrón basado en información de varios "dioses" excelentes de .NET, las Directrices de diseño de .NET Framework (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 (Arquitecto CLR en ese momento) y Brad Abrams (Creo que el Administrador del programa CLR en ese momento) y Bill Wagner ([C # efectivo] y [C # más efectivo] (solo tome un búsquelos 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 Finalizador 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 solo hilo). Además, una nota muy importante ... ¡todo el código ejecutado dentro de un Finalizador (si necesita implementar uno) DEBE ser seguro para subprocesos y para excepciones! MALAS cosas sucederán de otra manera ... (es decir, comportamiento indeterminado y, en el caso de una excepción, un bloqueo fatal irrecuperable de la aplicación).

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

#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 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

Dave Black
fuente
¿Alguien también puede agregar un patrón para una clase derivada (derivada de esta clase base)
Akjoshi
3
@akjoshi: he actualizado el patrón anterior para incluir el código de una clase desechable derivada. También tenga en cuenta, NUNCA implemente un Finalizador en una clase derivada ...
Dave Black
3
A Microsoft parece gustarle configurar el indicador "desechado" al final del método desechado, pero eso me parece incorrecto. Se supone que las llamadas redundantes a "Eliminar" no hacen nada; aunque normalmente no se esperaría que llamaran a Dispose de forma recursiva, tales cosas podrían suceder si se trata de deshacerse de un objeto que ha quedado en un estado no válido por una excepción que ocurrió durante la construcción u otra operación. Creo que usar un indicador de Interlocked.Exchangeentero IsDisposeden la función de envoltura no virtual sería más seguro.
supercat
@DaveBlack: ¿Qué sucede si su clase base no usa recursos no administrados, pero su clase derivada sí? ¿Tienes que implementar el Finalizador en la clase derivada entonces? Y si es así, ¿cómo sabe que la clase base aún no lo implementó si no tiene acceso a la fuente?
Didier A.
@DaveBlack "Desarrollé este patrón basado en información de varios" dioses ".NET excelentes" "Si uno de los dioses fuera Jon Skeet, seguiré tu consejo.
Elisabeth
23

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ífico, hay alrededor de 5 veces en las que necesitaría / debería implementar IDisposable:

  1. Su clase contiene explícitamente (es decir, no a través de la 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 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 () y 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 "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 recuento de referencias debajo de las cubiertas.

  4. Si su clase se suscribe a eventos usando referencias fuertes. Debe anular el registro / separarse de los eventos. ¡Siempre asegúrese de que estos no sean nulos antes de intentar anular el registro / 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í http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Una nota muy importante es que si está trabajando con WCF y limpiando recursos, SIEMPRE debe evitar el bloque 'usar'. Hay muchas publicaciones de blog y algunas en MSDN sobre por qué es una mala idea. También he publicado sobre esto aquí: no use 'using ()' con un proxy WCF

Dave Black
fuente
3
Creo que hay un quinto caso: si su clase se suscribe a eventos utilizando referencias sólidas, entonces debe implementar IDisposable y anular el registro de los eventos en el método Dispose.
Didier A.
Hola didibus Sí, estás en lo correcto. Me olvidé de eso. He modificado mi respuesta para incluir eso como un caso. Gracias.
Dave Black el
La documentación de MSDN para el patrón de eliminación agrega otro caso: "CONSIDERE implementar el Patrón de eliminación básico en clases que no contienen recursos no administrados u objetos desechables pero que probablemente tengan subtipos que sí lo tengan. Un gran ejemplo de esto es el System.IO .Stream class. Aunque es una clase base abstracta que no contiene ningún recurso, la mayoría de sus subclases sí y debido a esto, implementa este patrón ".
Gonen I
12

Usando lambdas en lugar de IDisposable.

Nunca me ha encantado 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 usar un método de fábrica y una lambda en su lugar

Imagine que quiero hacer algo con un SqlConnection (algo que debería envolverse 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, la persona que llama debe invocar DoWithConnection.

DoWithConnection se ve así

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

MakeConnection ahora es privado

pm100
fuente
2
Envolver cosas en lambdas puede ser un buen enfoque, pero tiene límites. No es tan malo para situaciones en las que, de hecho, todos los consumidores de una clase emplearían un bloque "en uso", pero no permitirían situaciones en las que un método almacenaría un ID disponible en un campo de clase (ya sea directamente o en un iterador )
supercat
@supercat puede argumentar que no permitir el almacenamiento de recursos que acaparan recursos es algo bueno. El modelo de endeudamiento que propongo aquí se obliga a ser delgado, con el uso del recurso
PM100
Puede ser algo bueno, pero también puede dificultar algunas operaciones muy razonables. Por ejemplo, suponga que un tipo de lector de base de datos, en lugar de implementar IEnumerable <T>, expone un método DoForAll(Action<T>) where T:IComparable<T>, llamando al delegado indicado en cada registro. Dados dos de estos objetos, los cuales devolverán datos en orden ordenado, ¿cómo generaría uno todos los elementos que existen en una colección pero no en el otro? Si los tipos implementados IEnumerable<T>, uno podría realizar una operación de fusión, pero eso no funcionará DoForAll.
supercat
La única forma en que puedo combinar dos DoForAllcolecciones sin tener que copiar primero una, en su totalidad, en alguna otra estructura, sería usar dos hilos, que serían recursos bastante más pesados ​​que simplemente usar un par de IEnumerable y tener cuidado para liberarlos.
supercat
-1: buena respuesta a una pregunta que no se hizo. Esta sería una gran respuesta a "¿cómo hago más fácil el consumo de objetos desechables ID?"
John Saunders
10

nadie respondió a 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 'using'. La pregunta que haría 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 a ellos por experiencia (una clase de socket por ejemplo)
  • está documentado
  • son cautelosos y pueden ver que la clase implementa IDisposable

Entonces, al implementar IDisposable, le está diciendo a los desarrolladores (al menos algunos) que esta clase concluye algo que debe ser liberado. Usarán 'usar', pero hay otros casos en los que no es posible usar (el alcance 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 el uso, pero no usarán el uso a menos que se lo indique.

Así que no lo hagas

pm100
fuente
1
No entiendo por qué un desarrollador no usaría el uso / disposición en un objeto que implementa IDisposable (a menos que el programa esté a punto de salir de todos modos).
adrianm
1
El punto es que un desarrollador tendría que escribir todas las llamadas para disponer en todas las rutas de código que resultan en la no referencia de él. SO, por ejemplo, si pongo una instancia en un Diccionario, cuando elimino entradas del diccionario, tengo que llamar a dispose. Es un montón de problemas que no se necesitan en este caso, el objeto no necesita ser eliminado
pm100
3
@ pm100 Re: Implementación innecesaria de IDisposable: hay un artículo detallado en codeproject.com/KB/dotnet/idisposable.aspx que analiza algunos casos raros en los que es posible que desee pensar en esto (muy raro, estoy seguro). En resumen: si puede prever la necesidad de IDisposable en el futuro, o en un objeto derivado, entonces podría pensar en implementar IDisposable como "no-op" en su clase base para evitar problemas de "corte" donde algunos objetos derivados requieren eliminación y otros no.
Kevin P. Rice
4
  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 se llama a Dispose en su objeto y se detiene allí.

  2. Si su clase no utiliza recursos escasos, no entiendo por qué haría que su clase implemente IDisposable. Solo debes hacerlo si eres:

    • Tenga en cuenta que pronto tendrá recursos escasos 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 su objeto puede usar usingcomo lo ha mostrado.

  4. (¿2 nuevamente?) Es probable que el WebClient utilice recursos no administrados u otros recursos administrados que implementan IDisposable. La razón exacta, sin embargo, no es importante. Lo importante es que implemente IDisposable, por lo que le corresponde actuar de acuerdo con ese conocimiento al deshacerse del objeto cuando haya terminado con él, incluso si resulta que WebClient no utiliza ningún otro recurso.

Lasse V. Karlsen
fuente
4

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();
        }
    }
}
Andrei Krasutski
fuente
4

Algunos aspectos de otra respuesta son ligeramente incorrectos 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. Llame a FactoryClass que devuelve un recurso IDisposable o 2. Si tiene un tipo que puede heredar o no de IDisposable dependiendo de su implementación, recuerde que he visto que el patrón IDisposable se implementó incorrectamente en muchos veces en muchos clientes donde los desarrolladores simplemente agregan un método Dispose () sin heredar de IDisposable (bad, bad, bad). También podría tener el caso de un recurso IDisposable que se devuelve desde una propiedad o método (nuevamente malo, malo, malo, no regale sus recursos IDisposable)

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

Si el operador 'as' 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 el cheque nulo 'incorporado'.

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

Se llama a un finalizador para que el GC destruya su objeto

Primero, la finalización (al igual que 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 he publicado anteriormente) y se ha llamado GC.SuppressFinalize (), NO se llamará al Finalizador. Esta es una de las grandes razones para implementar correctamente el patrón correctamente. Dado que solo hay 1 subproceso Finalizador por proceso administrado, independientemente del número de procesadores lógicos, puede degradar fácilmente el rendimiento haciendo una copia de seguridad o incluso colgando el subproceso Finalizador olvidando 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

Dave Black
fuente
2
¿Estás seguro de escribir NoGateway = new NoGateway();y NoGateway != null?
Cœur
1
¿Se refería a stackoverflow.com/a/898856/3195477 ? No hay respuesta publicada ahora por el nombre 'Icey'
UuDdLrLrSs
@DaveInCaz parece que eso es correcto. No veo 'Icey' en ningún lado, pero el contexto de mi respuesta parece estar dirigido a la respuesta proporcionada por su enlace anterior. Tal vez él cambió su nombre de usuario?
Dave Black el
@DaveBlack genial, gracias. Acabo de editar eso directamente en el texto.
UuDdLrLrSs
2

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 eliminen () de su clase NoGateway y el tipo nativo (que no es recolectado por el GC) deba limpiarse después. En este caso, si el usuario no llama a Dispose (), el GC eliminará el WebClient contenido inmediatamente 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 Eliminar () muy fácilmente.

Jesse C. Slicer
fuente
2

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);
         }
      }
   }
}
devnull
fuente
1
using(NoGateway objNoGateway = new NoGateway())

es equivalente a

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

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

Daniel Fabian
fuente
1
No se llama a un finalizador cuando el GC destruye el objeto. Si se anula "Finalizar", cuando el GC hubiera destruido el objeto , se colocará en una cola de objetos que necesitan finalización, creando temporalmente una fuerte referencia a él y, al menos temporalmente, "resucitándolo".
supercat
-5

Por lo que sé, es muy recomendable NO usar el Finalizador / 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 usted usa o elimina directamente.

usar es bueno. úsalo :)

Nic Wise
fuente
2
Debe seguir el enlace en la respuesta de thecoop. Sí, usar / Dispose es mejor, pero una clase Disposable definitivamente debería implementar ambos.
Henk Holterman
Interesante, todos los documentos que he leído de Microsoft, por ejemplo, las pautas de diseño del marco, dicen que NUNCA use un destructor. Siempre use IDisposable.
Nic Wise
55
Simplemente distinga entre usar una clase y escribir la clase, luego léalos nuevamente.
Henk Holterman
stackoverflow.com/questions/2605412/… puede ayudar
Alex Nolasco