2010-05-20 9 views
21

Considere el siguiente código:Eliminar, ¿cuándo se llama?

namespace DisposeTest 
{ 
    using System; 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("Calling Test"); 

      Test(); 

      Console.WriteLine("Call to Test done"); 
     } 

     static void Test() 
     { 
      DisposeImplementation di = new DisposeImplementation(); 
     } 
    } 

    internal class DisposeImplementation : IDisposable 
    { 
     ~DisposeImplementation() 
     { 
      Console.WriteLine("~ in DisposeImplementation instance called"); 
     } 
     public void Dispose() 
     { 
      Console.WriteLine("Dispose in DisposeImplementation instance called"); 
     } 
    } 
} 

El Desechar nunca acaba de llegar de llamada, incluso si pongo un bucle de espera después de la Test(); invocación. Así que eso apesta. Quiero escribir una clase que sea sencilla y muy fácil de usar, para asegurarme de que se limpien todos los recursos posibles. No quiero responsabilizar al usuario de mi clase.

Solución posible: use using, o llame a Eliminarme (básicamente lo mismo). ¿Puedo forzar al usuario a usar un uso? ¿O puedo forzar la disposición a ser llamado?

Llamar a GC.Collect(); después de Test(); tampoco funciona.

Poner di en null no invoca Desechar cualquiera. El Deconstructor FUNCIONA, por lo que el objeto se deconstruye cuando sale Test()

Bien chicos, ¡está claro ahora!

¡Gracias a todos por sus respuestas! ¡Agregaré una advertencia en el comentario!

Respuesta

17

Quiero escribir una clase que es sencillo y muy fácil de usar, para asegurarse de que todos los recursos posibles se limpia. No quiero para responsabilizar al usuario de mi clase.

No se puede hacer eso. La administración de la memoria simplemente no está diseñada para acomodar a desarrolladores desorientados.

El patrón IDisposable está destinado a los desarrolladores como una forma de contar un objeto cuando han terminado con él, en lugar de tener que gestionar la memoria tratando de resolverlo utilizando elementos como el recuento de referencias.

Puede utilizar el finalizador como un mensaje para los usuarios que no pueden disponer adecuadamente los objetos, pero no funciona bien como el método principal para la limpieza de objetos. Para trabajar sin problemas, los objetos se deben desechar de forma adecuada, de modo que no sea necesario llamar al Finalizer, que es más costoso.

+3

Pero tenga cuidado de no llamar a ningún otro objeto en su finalizador. El orden de finalización no está definido (especialmente si se permiten las dependencias cruzadas circulares y otras), por lo que lo único que se permite hacer a un finalizador es limpiar los recursos no administrados. – Cygon

+6

¿Por qué el voto a favor? Si no explica lo que piensa que está mal, no puede mejorar la respuesta. – Guffa

+0

No creo que Cygon sea absolutamente correcto en esto, ya que MSDN dice esto: "NO acceda a ningún objeto finalizable en la ruta del código del finalizador, porque existe un riesgo significativo de que ya se hayan finalizado". Nota, dice FINALIZABLE, no CUALQUIERA. No todos los objetos son finalizables por diseño. (referencia: https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx) – Califf

1

Se supone que debe desecharlo usted mismo, ya sea llamando al método Dispose o usando using. Recuerde, ¡no es un deconstructor!

Si no puede confiar en que los usuarios de su clase se deshagan de los recursos correctamente, es probable que se equivoquen de otras formas.

+0

"Un error común que las personas cometen cuando intentan diseñar algo completamente infalible es subestimar el ingenio de los tontos completos." - Douglas Adams, Mostly Harmless – yoyo

1

Eliminar no se llama automáticamente. Debe usar una cláusula using para ajustar el uso o llamarlo manualmente.

Ver http://msdn.microsoft.com/en-us/library/aa664736%28VS.71%29.aspx

Y sólo para adelantarse otra idea que pueda tener: no se puede llamar dispose del destructor ... He intentado esto hace algún tiempo en un proyecto.

+0

No existe un destructor en C#. Se llaman finalizadores. Puede llamarlo desde el finalizador (ver mi respuesta), pero puede haber precauciones especiales que debe tomar. – Thorarin

+1

C# sí tiene destructores: vea las especificaciones del lenguaje o http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx –

+1

@RodrickChapman: vea la respuesta de Dave Black. Usan la misma denominación '~ ClassName' como destructores de C++, pero no son destructores, son finalizadores. El compilador de C# toma el código en el llamado-pero-no-realmente-un-destructor y lo usa para implementar una sobrecarga de 'Object.Finalize()' (también agrega algunas cosas automáticamente) –

2

Tendrá que llamar explícitamente Dispose o envolviendo el objeto en una declaración using. Ejemplo:

using (var di = new DisposeImplementation()) 
{ 
} 

Posible solución: el uso el uso de, o llame Disponer mí mismo (básicamente la misma).

Usando using es lo mismo que llamar Dispose dentro de un bloque finally.

+0

La única vez a ' La instrucción using() no debe utilizarse en el caso de un Cliente/Proxy de WCF. Tengo una publicación en mi blog que explica por qué y proporciono una solución para deshacerse de un cliente/proxy de WCF. http://dave-black.blogspot.com/2012/03/dont-use-using-with-wcf-proxy.html –

13

Todas las respuestas son (más o menos) correcta, aquí está un ejemplo:

static void Test() 
{ 
    using (DisposeImplementation di = new DisposeImplementation()) 
    { 
     // Do stuff with di 
    } 
} 

llamar manualmente Dispose también funcionará, pero la ventaja de la declaración using es que el objeto también será eliminada cuando se abandone el bloque de control porque se lanza una excepción.

Se podría añadir un finalizador que maneja el recurso de disponer de alguien "se olvida" de caso de utilizar la interfaz IDisposable:

public class DisposeImplementation : 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 
    } 

    ~DisposeImplementation() 
    { 
     Dispose(false); 
    } 
} 

Ver this question para obtener información adicional. Sin embargo, esto es solo una compensación para las personas que no usan su clase correctamente :) Sugiero que agregue una gran llamada Debug.Fail() al finalizador, para advertir al desarrollador de su error.

Si decide implementar el patrón, se verá que GC.Collect() activará disposición.

7

utilizar esto como un patrón/plantilla para sus clases

public class MyClass : IDisposable 
{ 
    private bool disposed = false; 

    // Implement IDisposable. 
    // 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. 
     GC.SuppressFinalize(this); 
    } 

    // Dispose(bool disposing) executes in two distinct scenarios. 
    // If disposing equals true, the method has been called directly 
    // or indirectly by a user's code. Managed and unmanaged resources 
    // can be disposed. 
    // If disposing 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. 
    private void Dispose(bool disposing) 
    { 
     // Check to see if Dispose has already been called. 
     if (!this.disposed) 
     { 
      // If disposing equals true, dispose all managed 
      // and unmanaged resources. 
      if (disposing) 
      { 
       // Dispose managed resources.     
       ...... 
      } 

      // Call the appropriate methods to clean up 
      // unmanaged resources here. 
      // If disposing is false, 
      // only the following code is executed. 
      ........................... 

      // Note disposing has been done. 
      disposed = true; 
     } 
    } 

    // Use C# destructor syntax for finalization code. 
    // This destructor will run only if the Dispose method 
    // does not get called. 
    // It gives your base class the opportunity to finalize. 
    // Do not provide destructors in types derived from this class. 
    ~MyClass() 
    { 
     // Do not re-create Dispose clean-up code here. 
     // Calling Dispose(false) is optimal in terms of 
     // readability and maintainability. 
     Dispose(false); 
    } 
} 

Y, por supuesto, como han mencionado otros no se olvide de using(...){} bloque.

+0

Olvidó mencionar que nunca implemente un Finalizer a menos que esté trabajando con recursos * no administrados *. –

+0

Aunque si no necesita un Finalizer, todo el patrón SuprimirFinalizar/eliminar es redundante. – yoyo

+0

@yoyo - Absolutamente no es cierto. El hecho de que no necesite un finalizador no significa que no deba implementar IDisposable. Un Finalizador (así como el patrón Dispose) es necesario para la limpieza/eliminación de recursos no administrados. El patrón/implementación de Dispose debe implementarse cada vez que necesite limpiar los recursos administrados. –

41

Un par de puntos importantes se debe hacer para hacer frente a la pregunta de la OP:

  1. .NET GC es no determinista (es decir, nunca se sabe ni debe depender de cuando sucede)
  2. Desechar es nunca llamado por .NET Framework; debe llamarlo manualmente, preferiblemente envolviendo su creación en un bloque using().
  3. establecer explícitamente un objeto desechable para nula sin llamar a Dispose() en él es una mala cosa que hacer. Lo que sucede es que establece explícitamente los objetos "raíz de referencia" en nulo. Esto significa que no puede llamar a Dispose más tarde Y, lo que es más importante, envía el objeto a GC Finalization Queue for Finalization. Causar la finalización por malas prácticas de programación debe evitarse a toda costa.

Finalizer: Algunos desarrolladores se refieren a ella como un destructor. Y de hecho incluso se llama un Destructor en el C# 4.0 Language Spec (section 1.6.7.6) y en versiones anteriores del actual ECMA-334 spec. Afortunadamente, la 4ª Edición (junio de 2006) define correctamente los Finalizadores en la Sección 8.7.9 e intenta aclarar la confusión entre los dos en la Sección 17.12.Cabe señalar que existen diferencias internas importantes (no es necesario entrar en detalles sangrientos) entre lo que tradicionalmente se conoce como destructor y Destructor/Finalizer en .NET Framework.

  1. Si un finalizador está presente, entonces se llamará por .NET Framework si y sólo si GC.SuppressFinalize() no se llama.
  2. NUNCA debe llamar explícitamente a un finalizador. Afortunadamente, C# no permitirá explícitamente esto (no sé sobre otros idiomas); aunque se puede forzar llamando al GC.Collect(2) para la 2ª generación del GC.

Finalización: Finalización es la manera del .NET Framework para hacer frente a la limpieza 'gracia' y la liberación de los recursos.

  1. Solo se produce cuando hay objetos en la cola de finalización.
  2. Se produce solo cuando se produce una recolección de basura para Gen2 (que es aproximadamente 1 de cada 100 colecciones para una aplicación .NET bien escrita).
  3. Hasta e incluyendo .NET 4, hay una única cadena de finalización. Si este hilo se bloquea por alguna razón, su aplicación está jodida.
  4. Escribir código correcto y seguro finalización no es trivial y se pueden cometer errores con bastante facilidad (es decir, accidentalmente permitiendo excepciones a ser lanzados desde el Finalizer, permitiendo dependencias de otros objetos que ya puede ser cerrado, etc.)

Si bien esta es sin duda más información que usted pidió, proporciona información general sobre cómo funcionan las cosas y por qué funcionan de la manera en que lo hacen. Algunas personas argumentarán que no deberían tener que preocuparse por administrar la memoria y los recursos en .NET, pero eso no cambia el hecho de que debe hacerse, y no veo que eso desaparezca en el futuro cercano.

+0

"* Dispose nunca es llamado por .NET Framework; debe llamarlo manualmente *" - la instrucción 'using' llama' Dispose' internamente. – James

+5

sí, una instrucción 'using' es efectivamente una try/finally con una llamada a Dispose en la cláusula finally. Mi punto era que tienes que * escribir el código con * la declaración 'using()' o llamar a Dispose. En otras palabras, si no ajusta el uso del recurso desechable dentro de una declaración 'using()', puede filtrar un recurso o colocarlo en la cola para la Finalización. –

+2

Sí, mi punto era que el comentario es * ligeramente * engañoso para los lectores, podrías actualizar para decir "* Dispose nunca es llamado por .NET Framework; debes llamarlo manualmente ** o envolver el objeto desechable con un' using' declaración *** "o algo por el estilo ... – James

Cuestiones relacionadas