Pregunta

La red Patrón desechable implica que si escribe un finalizador e implementa IDisposable, su finalizador debe llamar explícitamente a Dispose.Esto es lógico y es lo que siempre he hecho en las raras situaciones en las que se justifica un finalizador.

Sin embargo, ¿qué sucede si solo hago esto?

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

y no implementar un finalizador, ni nada por el estilo.¿El marco llamará al método Dispose por mí?

Sí, me doy cuenta de que esto suena tonto, y toda la lógica implica que no lo será, pero siempre he tenido dos cosas en la cabeza que me han hecho sentir inseguro.

  1. Hace unos años alguien me dijo una vez que de hecho haría esto, y esa persona tenía un historial muy sólido de "saber lo que hace".

  2. El compilador/marco hace otras cosas "mágicas" dependiendo de las interfaces que implemente (por ejemplo:foreach, métodos de extensión, serialización basada en atributos, etc.), por lo que tiene sentido que esto también sea "mágico".

Si bien he leído muchas cosas al respecto y hay muchas cosas implícitas, nunca he podido encontrar una definitivo Respuesta Sí o No a esta pregunta.

¿Fue útil?

Solución

.Net Garbage Collector llama al método Object.Finalize de un objeto en la recolección de basura.Por por defecto esto hace nada y debe anularse si desea liberar recursos adicionales.

Dispose NO se llama automáticamente y debe ser explícitamente Se llama si se van a liberar recursos, como dentro de un bloque 'usando' o 'intentar finalmente'.

ver http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx para más información

Otros consejos

Quiero enfatizar el punto de Brian en su comentario, porque es importante.

Los finalizadores no son destructores deterministas como en C++.Como otros han señalado, no hay garantía de cuándo se llamará y, de hecho, si tiene suficiente memoria, si lo hará. alguna vez ser llamado.

Pero lo malo de los finalizadores es que, como dijo Brian, hacen que su objeto sobreviva a una recolección de basura.Esto puede ser malo.¿Por qué?

Como quizás sepas o no, el GC se divide en generaciones: Gen 0, 1 y 2, más el montón de objetos grandes.Dividir es un término vago: obtienes un bloque de memoria, pero hay indicaciones de dónde comienzan y terminan los objetos Gen 0.

El proceso de pensamiento es que probablemente usarás muchos objetos que durarán poco.Por lo tanto, debería ser fácil y rápido para el GC acceder a ellos: objetos Gen 0.Entonces, cuando hay presión de memoria, lo primero que hace es una colección Gen 0.

Ahora, si eso no resuelve la presión suficiente, entonces regresa y hace un barrido Gen 1 (rehaciendo Gen 0), y luego, si aún no es suficiente, hace un barrido Gen 2 (rehaciendo Gen 1 y Gen 0).Por lo tanto, limpiar objetos de larga duración puede llevar un tiempo y resultar bastante costoso (ya que los hilos pueden quedar suspendidos durante la operación).

Esto significa que si haces algo como esto:

~MyClass() { }

Tu objeto, pase lo que pase, vivirá hasta la Generación 2.Esto se debe a que el GC no tiene forma de llamar al finalizador durante la recolección de basura.Entonces, los objetos que deben finalizarse se mueven a una cola especial para que un hilo diferente los limpie (el hilo finalizador, que si lo matas hace que sucedan todo tipo de cosas malas).Esto significa que sus objetos permanecen más tiempo y potencialmente fuerzan más recolecciones de basura.

Entonces, todo esto es solo para aclarar el punto de que desea usar IDisposable para limpiar recursos siempre que sea posible y tratar seriamente de encontrar formas de evitar el uso del finalizador.Es lo mejor para su aplicación.

Ya hay muchas buenas discusiones aquí y llego un poco tarde a la fiesta, pero yo mismo quería agregar algunos puntos.

  • El recolector de basura nunca ejecutará directamente un método Dispose por usted.
  • la general voluntad ejecutar finalizadores cuando lo desee.
  • Un patrón común que se utiliza para objetos que tienen un finalizador es hacer que llame a un método que se define por convención como Dispose(bool disposing) pasando false para indicar que la llamada se realizó debido a la finalización en lugar de una llamada Dispose explícita.
  • Esto se debe a que no es seguro hacer suposiciones sobre otros objetos administrados mientras se finaliza un objeto (es posible que ya se hayan finalizado).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Esa es la versión simple, pero hay muchos matices que pueden hacer que este patrón te haga tropezar.

  • El contrato para IDisposable.Dispose indica que debe ser seguro llamar varias veces (llamar a Dispose en un objeto que ya fue eliminado no debería hacer nada)
  • Puede resultar muy complicado gestionar adecuadamente una jerarquía de herencia de objetos desechables, especialmente si diferentes capas introducen nuevos recursos desechables y no administrados.En el patrón anterior, Dispose(bool) es virtual para permitir que se anule y pueda administrarse, pero creo que es propenso a errores.

En mi opinión, es mucho mejor evitar por completo cualquier tipo que contenga directamente referencias desechables y recursos nativos que puedan requerir finalización.SafeHandles proporciona una forma muy limpia de hacer esto al encapsular recursos nativos en recursos desechables que proporcionan internamente su propia finalización (junto con una serie de otros beneficios como eliminar la ventana durante P/Invoke donde un identificador nativo podría perderse debido a una excepción asincrónica). .

Simplemente definir un SafeHandle hace que esto sea trivial:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Le permite simplificar el tipo contenedor a:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

No me parece.Usted tiene control sobre cuándo se llama a Dispose, lo que significa que, en teoría, podría escribir código de eliminación que haga suposiciones sobre (por ejemplo) la existencia de otros objetos.No tiene control sobre cuándo se llama al finalizador, por lo que sería dudoso que el finalizador llame automáticamente a Dispose en su nombre.


EDITAR:Fui y probé, solo para asegurarme:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

No en el caso que describe, pero el GC llamará al Finalizador para ti, si tienes uno.

SIN EMBARGO.En la siguiente recolección de basura, en lugar de recolectarse, el objeto irá a la cola de finalización, todo se recolecta y luego se llama al finalizador.La próxima colección posterior será liberada.

Dependiendo de la presión de la memoria de su aplicación, es posible que no tenga un gc para la generación de ese objeto por un tiempo.Entonces, en el caso de, por ejemplo, una secuencia de archivos o una conexión de base de datos, es posible que tenga que esperar un tiempo para que el recurso no administrado se libere en la llamada del finalizador, lo que causa algunos problemas.

No, no se llama.

Pero esto hace que sea más fácil no olvidarse de deshacerse de sus objetos.Sólo usa el using palabra clave.

Hice la siguiente prueba para esto:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

La Generalitat no llamar a disponer.Él puede Llame a su finalizador, pero ni siquiera esto está garantizado en todas las circunstancias.

Mira esto artículo para una discusión sobre la mejor manera de manejar esto.

La documentación sobre Desechable ofrece una explicación bastante clara y detallada del comportamiento, así como un código de ejemplo.El CG NO llamará al Dispose() método en la interfaz, pero llamará al finalizador de su objeto.

El patrón IDisposable se creó principalmente para que lo llame el desarrollador; si tiene un objeto que implementa IDispose, el desarrollador debe implementar el using palabra clave alrededor del contexto del objeto o llame al método Dispose directamente.

La seguridad para el patrón es implementar el finalizador llamando al método Dispose().Si no lo hace, puede crear algunas pérdidas de memoria, es decir:Si crea algún contenedor COM y nunca llama a System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (que se colocaría en el método Dispose).

No hay magia en clr para llamar a los métodos Dispose automáticamente, aparte de rastrear objetos que contienen finalizadores y almacenarlos en la tabla Finalizer por parte del GC y llamarlos cuando el GC activa algunas heurísticas de limpieza.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top