c# ejemplo - Uso adecuado de la interfaz IDisposable.




implementar how (17)

El punto de Dispose es liberar recursos no administrados. Debe hacerse en algún momento, de lo contrario nunca se limpiarán. El recolector de basura no sabe cómo llamar a DeleteHandle() en una variable de tipo IntPtr , no sabe si debe llamar a DeleteHandle() o no.

Nota : ¿Qué es un recurso no administrado ? Si lo encontraste en Microsoft .NET Framework: está administrado. Si fuiste a buscar en MSDN, no está administrado. Cualquier cosa que haya usado P / Invoke llama para salir del agradable y cómodo mundo de todo lo que tiene disponible en .NET Framwork no está administrado, y ahora usted es responsable de limpiarlo.

El objeto que ha creado necesita exponer algún método, al que el mundo exterior puede llamar, para limpiar los recursos no administrados. El método puede ser nombrado como quieras:

public void Cleanup()

public void Shutdown()

Pero en cambio, hay un nombre estandarizado para este método:

public void Dispose()

Incluso se creó una interfaz, IDisposable , que tiene solo ese método:

public interface IDisposable
{
   void Dispose()
}

Así que haces que tu objeto exponga la interfaz IDisposable , y de esa manera prometes que has escrito ese único método para limpiar tus recursos no administrados:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

Y tu estas listo. Excepto que puedes hacerlo mejor.

¿Qué sucede si su objeto ha asignado un System.Drawing.Bitmap 250 MB (es decir, la clase de mapa de bits administrada por .NET) como algún tipo de búfer de marco? Claro, este es un objeto .NET administrado, y el recolector de basura lo liberará. Pero, ¿realmente quieres dejar 250 MB de memoria simplemente sentado allí, esperando que el recolector de basura finalmente llegue y lo libere? ¿Qué pasa si hay una conexión de base de datos abierta ? Seguramente no queremos que la conexión quede abierta, esperando que el CG finalice el objeto.

Si el usuario ha llamado a Dispose() (lo que significa que ya no planea usar el objeto), ¿por qué no deshacerse de esos mapas de bits y conexiones de base de datos inútiles?

Así que ahora vamos a:

  • deshacerse de los recursos no administrados (porque tenemos que hacerlo), y
  • deshacerse de los recursos gestionados (porque queremos ser de ayuda)

Así que actualicemos nuestro método Dispose() para deshacernos de esos objetos administrados:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

Y todo está bien, excepto que puedes hacerlo mejor !

¿Qué sucede si la persona olvidó llamar a Dispose() en su objeto? ¡Entonces filtrarían algunos recursos no administrados !

Nota: No perderán recursos administrados , porque eventualmente el recolector de basura se ejecutará en un subproceso en segundo plano y liberará la memoria asociada con los objetos no utilizados. Esto incluirá su objeto y cualquier objeto administrado que use (por ejemplo, el Bitmap y la DbConnection ).

Si la persona se olvidó de llamar a Dispose() , todavía podemos guardar su tocino. Todavía tenemos una manera de llamarlo por ellos: cuando el recolector de basura finalmente consigue liberar (es decir, finalizar) nuestro objeto.

Nota: El recolector de basura eventualmente liberará todos los objetos gestionados. Cuando lo hace, llama al método Finalize en el objeto. El GC no sabe, o le importa, su método de Disposición . Ese fue solo el nombre que elegimos para un método que llamamos cuando queremos deshacernos de cosas no administradas.

La destrucción de nuestro objeto por parte del recolector de basura es el momento perfecto para liberar esos recursos molestos no administrados. Hacemos esto anulando el método Finalize() .

Nota: En C #, no reemplaza explícitamente el método Finalize() . Escribe un método que se parece a un destructor de C ++ , y el compilador considera que esa es su implementación del método Finalize() :

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Pero hay un error en ese código. Usted ve, el recolector de basura se ejecuta en un hilo de fondo ; no sabes el orden en que se destruyen dos objetos. Es completamente posible que en su código de Dispose() , el objeto administrado que intenta deshacerse (porque quería ser útil) ya no esté allí:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Entonces, lo que necesita es una forma para que Finalize() diga a Dispose() que no debe tocar ningún recurso administrado (ya que es posible que ya no esté allí ), mientras sigue liberando recursos no administrados.

El patrón estándar para hacer esto es que Finalize() y Dispose() llamen a un tercer método (!); donde pasa un dicho booleano si lo llama desde Dispose() (en lugar de Finalize() ), lo que significa que es seguro administrar recursos administrados de forma gratuita.

A este método interno se le podría dar un nombre arbitrario como "CoreDispose", o "MyInternalDispose", pero es tradicional llamarlo Dispose(Boolean) :

protected void Dispose(Boolean disposing)

Pero un nombre de parámetro más útil podría ser:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

Y cambia su implementación del método IDisposable.Dispose() para:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

y su finalizador para:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Nota : si su objeto desciende de un objeto que implementa Dispose , entonces no olvide llamar a su método de Disose base cuando anule Disose:

public Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

Y todo está bien, excepto que puedes hacerlo mejor !

Si el usuario llama a Dispose() en su objeto, entonces todo se ha limpiado. Más adelante, cuando el recolector de basura aparezca y llame a Finalizar, llamará nuevamente a Dispose .

No solo es un desperdicio, sino que si su objeto tiene referencias no deseadas a objetos que ya eliminó de la última llamada a Dispose() , ¡tratará de deshacerse de ellos nuevamente!

Notará que en mi código tuve cuidado de eliminar las referencias a los objetos que eliminé, por lo que no intento llamar a Dispose en una referencia de objeto no deseado. Pero eso no impidió que un error sutil se introdujera.

Cuando el usuario llama a Dispose() : el manejador CursorFileBitmapIconServiceHandle se destruye. Más tarde, cuando se ejecuta el recolector de basura, intentará destruir el mismo identificador de nuevo.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

La forma en que solucionas esto es decirle al recolector de basura que no necesita molestarse en finalizar el objeto: sus recursos ya se han limpiado y no se necesita más trabajo. Puede hacerlo llamando a GC.SuppressFinalize() en el método Dispose() :

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Ahora que el usuario ha llamado Dispose() , tenemos:

  • recursos liberados no administrados
  • recursos gestionados liberados

No tiene sentido que el GC ejecute el finalizador, todo está arreglado.

¿No podría usar Finalize para limpiar los recursos no administrados?

La documentación para Object.Finalize dice:

El método Finalizar se usa para realizar operaciones de limpieza en recursos no administrados mantenidos por el objeto actual antes de que se destruya el objeto.

Pero la documentación de MSDN también dice, para IDisposable.Dispose :

Realiza tareas definidas por la aplicación asociadas con la liberación, la liberación o el restablecimiento de recursos no administrados.

Entonces, ¿cuál es? ¿Cuál es el lugar para que yo limpie los recursos no administrados? La respuesta es:

¡Es tu elección! Pero elige Dispose .

Ciertamente podría colocar su limpieza no administrada en el finalizador:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

El problema con eso es que no tienes idea de cuándo llegará el recolector de basura a finalizar tu objeto. Sus recursos nativos no administrados, innecesarios, no utilizados se mantendrán hasta que el recolector de basura finalmente se ejecute. Entonces llamará a tu método finalizador; La limpieza de los recursos no gestionados. La documentación de Object.Finalize señala esto:

La hora exacta en que se ejecuta el finalizador no está definida. Para garantizar la liberación determinista de recursos para instancias de su clase, implemente un método de Cierre o proporcione una implementación de IDisposable.Dispose .

Esta es la virtud de utilizar Dispose to cleanup recursos no administrados; se llega a conocer y controlar cuando se limpian los recursos no administrados. Su destrucción es "determinista" .

Para responder a su pregunta original: ¿por qué no liberar memoria ahora, en lugar de para cuando el GC decide hacerlo? Tengo un software de reconocimiento facial que ahora necesita deshacerme de 530 MB de imágenes internas, ya que ya no son necesarias. Cuando no lo hacemos: la máquina se detiene en un intercambio.

Lectura de bonos

Para cualquier persona que le guste el estilo de esta respuesta (explicando el por qué , entonces el cómo se vuelve obvio), le sugiero que lea el Capítulo Uno de la COM esencial de Don Box:

En 35 páginas, explica los problemas de usar objetos binarios e inventa COM ante sus ojos. Una vez que te das cuenta del por qué de COM, las 300 páginas restantes son obvias y solo detallan la implementación de Microsoft.

Creo que todo programador que haya tratado con objetos o COM debería, como mínimo, leer el primer capítulo. Es la mejor explicación de cualquier cosa.

Lectura extra de bonificación

Cuando todo lo que sabes está mal por Eric Lippert

Por lo tanto, es muy difícil escribir un finalizador correcto, y el mejor consejo que puedo darte es que no lo intentes .

Sé por leer la documentación de MSDN que el uso "primario" de la interfaz IDisposable es limpiar los recursos no administrados.

Para mí, "no administrado" significa cosas como conexiones de base de datos, sockets, manejadores de ventanas, etc. Pero he visto un código donde se implementa el método Dispose() para liberar recursos administrados , lo que me parece redundante, ya que el recolector de basura debería cuida eso por ti

Por ejemplo:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Mi pregunta es, ¿esto hace que la memoria libre del recolector de basura utilizada por MyCollection sea ​​más rápida de lo normal?

editar : Hasta ahora, las personas han publicado algunos buenos ejemplos de uso de IDisposable para limpiar recursos no administrados, como conexiones de base de datos y mapas de bits. Pero suponga que _theList en el código anterior contenga un millón de cadenas, y quisiera liberar esa memoria ahora , en lugar de esperar al recolector de basura. ¿Lograría eso el código anterior?


Sí, ese código es completamente redundante e innecesario y no hace que el recolector de basura haga nada que de otra manera no haría (una vez que una instancia de MyCollection queda fuera del alcance, es decir) .Clear().

Respuesta a su edición: Algo así como. Si hago esto:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Es funcionalmente idéntico a esto para propósitos de administración de memoria:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Si realmente necesitas liberar la memoria en este instante, llama GC.Collect(). Aunque no hay razón para hacer esto aquí. La memoria se liberará cuando sea necesario.


Si MyCollection va a ser recogida de basura de todos modos, entonces no debería tener que desecharla. Hacerlo solo agitará la CPU más de lo necesario e incluso podría invalidar algunos análisis precalculados que el recolector de basura ya ha realizado.

Uso IDisposable para hacer cosas como garantizar que los subprocesos se eliminen correctamente, junto con los recursos no administrados.

EDITAR En respuesta al comentario de Scott:

La única vez que se ven afectadas las métricas de rendimiento del GC es cuando se realiza una llamada al [sic] GC.Collect () "

Conceptualmente, el GC mantiene una vista del gráfico de referencia del objeto, y todas las referencias a él desde los marcos de pila de hilos. Este montón puede ser bastante grande y abarcar muchas páginas de memoria. Como optimización, el GC almacena en caché su análisis de páginas que probablemente no cambiarán muy a menudo para evitar volver a escanear la página innecesariamente. El GC recibe una notificación del kernel cuando los datos en una página cambian, por lo que sabe que la página está sucia y requiere una reexploración. Si la colección está en Gen0, es probable que otras cosas en la página también estén cambiando, pero esto es menos probable en Gen1 y Gen2. Como anécdota, estos ganchos no estaban disponibles en Mac OS X para el equipo que portó el GC a Mac para que el complemento Silverlight funcione en esa plataforma.

Otro punto en contra de la eliminación innecesaria de recursos: imagine una situación en la que un proceso se está descargando. Imagina también que el proceso se ha estado ejecutando durante algún tiempo. Lo más probable es que muchas de las páginas de memoria de ese proceso se hayan cambiado a disco. Por lo menos ya no están en caché L1 o L2. En tal situación, no tiene sentido que una aplicación que se está descargando intercambie de nuevo todos esos datos y páginas de códigos en la memoria para 'liberar' recursos que el sistema operativo liberará de todos modos cuando el proceso finalice. Esto se aplica a los recursos administrados e incluso a ciertos recursos no administrados. Solo los recursos que mantienen vivos los subprocesos que no están en segundo plano deben eliminarse; de ​​lo contrario, el proceso seguirá vivo.

Ahora, durante la ejecución normal, hay recursos efímeros que deben limpiarse correctamente (ya que @fezmonkey señala conexiones de bases de datos, sockets, manijas de ventanas ) para evitar pérdidas de memoria no administradas. Estos son los tipos de cosas que deben ser eliminadas. Si creas una clase que posee un hilo (y me refiero a que lo creó y por lo tanto es responsable de asegurar que se detenga, al menos por mi estilo de codificación), entonces esa clase probablemente debe implementar IDisposabley arrancar el hilo durante Dispose.

.NET Framework usa la IDisposableinterfaz como una señal, incluso de advertencia, para los desarrolladores de que esta clase debe eliminarse. No puedo pensar en ningún tipo de marco que implemente IDisposable(excluyendo implementaciones de interfaz explícitas) donde la eliminación es opcional.


El caso de uso más justificable para la eliminación de recursos administrados, es la preparación para que el GC recupere recursos que de otra manera nunca se recopilarían.

Un buen ejemplo son las referencias circulares.

Si bien es una buena práctica usar patrones que eviten las referencias circulares, si terminas con (por ejemplo) un objeto 'secundario' que tiene una referencia a su 'padre', esto puede detener la recopilación de GC del padre si solo abandonas la referencia y confíe en GC - además, si ha implementado un finalizador, nunca se llamará.

La única forma de evitar esto es romper manualmente las referencias circulares configurando las referencias principales en nulas en los secundarios.

Implementar IDisposable en padres e hijos es la mejor manera de hacer esto. Cuando se llama a Dispose en el Padre, llame a Dispose en todos los Niños y, en el método de Dispose hijo, establezca las referencias de Padres en nulas.


No debería haber más llamadas a los métodos de un objeto después de que se haya llamado a Dispose (aunque un objeto debe tolerar más llamadas a Dispose). Por lo tanto el ejemplo en la pregunta es tonto. Si se llama Dispose, entonces el objeto en sí puede ser descartado. Por lo tanto, el usuario simplemente debe descartar todas las referencias a ese objeto completo (establecerlo en nulo) y todos los objetos relacionados internos a él se limpiarán automáticamente.

En cuanto a la pregunta general sobre administrado / no administrado y la discusión en otras respuestas, creo que cualquier respuesta a esta pregunta debe comenzar con una definición de un recurso no administrado.

Todo se reduce a que hay una función a la que se puede llamar para poner el sistema en un estado, y hay otra función a la que se puede llamar para que salga de ese estado. Ahora, en el ejemplo típico, la primera podría ser una función que devuelve un identificador de archivo, y la segunda podría ser una llamada a CloseHandle .

Pero, y esta es la clave, podrían ser cualquier par de funciones coincidentes. Uno construye un estado, el otro lo derriba. Si el estado se ha construido pero aún no se ha demolido, existe una instancia del recurso. Debe hacer los arreglos necesarios para que el desmontaje ocurra en el momento adecuado: CLR no administra el recurso. El único tipo de recurso administrado automáticamente es la memoria. Hay dos tipos: el GC, y la pila. Los tipos de valor son administrados por la pila (o al enganchar un viaje dentro de los tipos de referencia), y los tipos de referencia son administrados por el GC.

Estas funciones pueden causar cambios de estado que se pueden intercalar libremente, o pueden ser anidados perfectamente. Los cambios de estado pueden ser seguros para subprocesos o pueden no serlo.

Mira el ejemplo en la pregunta de la justicia. Los cambios en la sangría del archivo de registro deben estar perfectamente anidados, o todo irá mal. También es poco probable que sean seguros para hilos.

Es posible viajar con el recolector de basura para limpiar sus recursos no administrados. Pero solo si las funciones de cambio de estado son seguras para subprocesos y dos estados pueden tener tiempos de vida que se superpongan de cualquier manera. ¡Entonces el ejemplo de Justicia de un recurso NO debe tener un finalizador! Simplemente no ayudaría a nadie.

Para ese tipo de recursos, puede implementar IDisposable , sin un finalizador. El finalizador es absolutamente opcional, tiene que serlo. Esto se pasa por alto o ni siquiera se menciona en muchos libros.

Luego tiene que usar la instrucción using para tener alguna posibilidad de asegurarse de que se llame a Dispose . Esto es esencialmente como enganchar un paseo con la pila (así como el finalizador es para el GC, using es para la pila).

La parte faltante es que debe escribir manualmente Dispose y hacer que llame a sus campos y su clase base. Los programadores de C ++ / CLI no tienen que hacer eso. El compilador lo escribe para ellos en la mayoría de los casos.

Hay una alternativa, que prefiero para los estados que se anidan perfectamente y no son seguros para las hebras (aparte de todo lo demás, evitando IDisposable le ahorra el problema de tener una discusión con alguien que no puede resistirse a agregar un finalizador a cada clase que implementa IDisposable) .

En lugar de escribir una clase, escribes una función. La función acepta un delegado para devolver la llamada a:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Y entonces un ejemplo simple sería:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

La lambda que se pasa sirve como un bloque de código, por lo que es como hacer que su propia estructura de control sirva para el mismo propósito que el using , excepto que ya no tiene ningún peligro de que la persona que llama abusa de él. No hay forma de que no puedan limpiar el recurso.

Esta técnica es menos útil si el recurso es del tipo que puede tener tiempos de vida superpuestos, porque luego desea poder construir el recurso A, luego el recurso B, luego eliminar el recurso A y luego matar el recurso B. No puede hacer eso Si has forzado al usuario a anidar perfectamente así. Pero luego necesitas usar IDisposable (pero aún sin un finalizador, a menos que hayas implementado threadsafety, que no es gratis).


Un problema con la mayoría de las discusiones sobre "recursos no administrados" es que realmente no definen el término, pero parecen implicar que tiene algo que ver con el código no administrado. Si bien es cierto que muchos tipos de recursos no administrados interactúan con el código no administrado, pensar en recursos no administrados en tales términos no es útil.

En cambio, uno debería reconocer lo que todos los recursos administrados tienen en común: todos implican un objeto que pide a una "cosa" externa que haga algo en su nombre, en detrimento de otras "cosas", y la otra entidad acepta hacerlo hasta Aviso adicional. Si el objeto fuera abandonado y desapareciera sin dejar rastro, nada le diría a esa "cosa" externa que ya no necesitaba alterar su comportamiento en nombre del objeto que ya no existía; en consecuencia, la utilidad de la cosa sería disminuida permanentemente.

Un recurso no administrado, entonces, representa un acuerdo por parte de alguna "cosa" externa para alterar su comportamiento en nombre de un objeto, lo que inutilizaría la utilidad de esa "cosa" externa si el objeto se abandonara y dejara de existir. Un recurso administrado es un objeto que es el beneficiario de dicho acuerdo, pero que se ha registrado para recibir una notificación si se abandona, y que utilizará dicha notificación para poner sus asuntos en orden antes de que se destruya.


El propósito del patrón Dispose es proporcionar un mecanismo para limpiar tanto los recursos administrados como los no administrados, y cuando esto ocurre, depende de cómo se llame al método Dispose. En su ejemplo, el uso de Dispose no está haciendo nada relacionado con la disposición, ya que la eliminación de una lista no tiene ningún impacto en la eliminación de esa colección. Del mismo modo, las llamadas para establecer las variables en nulas tampoco tienen ningún impacto en el GC.

Puedes echar un vistazo a este article para obtener más detalles sobre cómo implementar el patrón de Disposición, pero básicamente se ve así:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

El método que es el más importante aquí es el Dispose (bool), que en realidad se ejecuta en dos circunstancias diferentes:

  • disposing == true: el método ha sido llamado directa o indirectamente por el código de un usuario. Los recursos administrados y no administrados pueden ser eliminados.
  • disposition == false: el tiempo de ejecución ha llamado al método desde dentro del finalizador, y no debe hacer referencia a otros objetos. Solo se pueden disponer los recursos no administrados.

El problema de simplemente dejar que el GC se encargue de realizar la limpieza es que no tiene un control real sobre cuándo el GC ejecutará un ciclo de recolección (puede llamar a GC.Collect (), pero realmente no debería), por lo que los recursos pueden permanecer. Alrededor más tiempo del necesario. Recuerde, llamar a Dispose () en realidad no causa un ciclo de recolección o de ninguna manera hace que el GC recolecte / libere el objeto; simplemente proporciona los medios para limpiar de manera más determinista los recursos utilizados y decirle al GC que esta limpieza ya se ha realizado.

Todo el punto de IDisposable y el patrón de disposición no se trata de liberar inmediatamente la memoria. La única vez que una llamada a Dispose realmente tendrá la posibilidad de liberar memoria de inmediato es cuando está manejando el escenario falso == y manipulando recursos no administrados. Para el código administrado, la memoria no se recuperará hasta que el GC ejecute un ciclo de recopilación, sobre el que realmente no tiene control (aparte de llamar a GC.Collect (), que ya mencioné no es una buena idea).

Su escenario no es realmente válido ya que las cadenas en .NET no usan ningún recurso sin modificar y no implementan IDisposable, no hay forma de obligarlas a que se "limpien"


IDisposable se utiliza a menudo para explotar la declaración de using y aprovechar una forma sencilla de realizar una limpieza determinista de los objetos gestionados.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

No repetiré las cosas habituales sobre el uso o la liberación de recursos no administrados, todo eso se ha cubierto. Pero me gustaría señalar lo que parece un error común.
Dado el siguiente código

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

Me doy cuenta de que la implementación de Desechables no sigue las pautas actuales, pero espero que todos tengan una idea.
Ahora, cuando se llama Dispose, ¿cuánta memoria se libera?

Respuesta: Ninguna.
Al llamar a Dispose puede liberar recursos no administrados, NO PUEDE reclamar la memoria administrada, solo el GC puede hacer eso. Eso no quiere decir que lo anterior no sea una buena idea, seguir el patrón anterior es, de hecho, una buena idea. Una vez que se ha ejecutado Dispose, no hay nada que impida que el GC vuelva a reclamar la memoria que estaba utilizando _Large, incluso aunque la instancia de LargeStuff aún pueda estar dentro del alcance. Las cadenas en _Large también pueden estar en gen 0, pero la instancia de LargeStuff podría ser gen 2, así que nuevamente, la memoria se volverá a reclamar antes.
Sin embargo, no tiene sentido agregar un finalizador para llamar al método Dispose que se muestra arriba. Eso solo retrasará la recuperación de la memoria para permitir que se ejecute el finalizador.


En el ejemplo que publicaste, todavía no "libera la memoria ahora". Toda la memoria se recolecta como basura, pero puede permitir que la memoria se recopile en una generation anterior . Tendrías que hacer algunas pruebas para estar seguro.

Las pautas de diseño del marco son pautas, y no reglas. Le dicen para qué sirve principalmente la interfaz, cuándo usarla, cómo usarla y cuándo no usarla.

Una vez leí el código que era un simple RollBack () en caso de falla utilizando IDisposable. La clase MiniTx a continuación verificará una marca en Dispose () y si la Commitllamada nunca se realizó, entonces se llamará Rollbacka sí misma. Añadió una capa de direccionamiento indirecto que hace que el código de llamada sea mucho más fácil de entender y mantener. El resultado se parecía a algo como:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

También he visto que el código de tiempo / registro hace lo mismo. En este caso, el método Dispose () detuvo el temporizador y registró que el bloque había salido.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Así que aquí hay un par de ejemplos concretos que no realizan ninguna limpieza de recursos no administrados, sino que se utilizan con éxito para crear un código más limpio.


Hay cosas que la Dispose()operación realiza en el código de ejemplo que podría tener un efecto que no se produciría debido a un GC normal del MyCollectionobjeto.

Si los objetos a los que hace referencia _theListo _theDictson referenciados por otros objetos, entonces ese List<>u Dictionary<>objeto no estará sujeto a la colección pero de repente no tendrá contenido. Si no hubiera una operación Dispose () como en el ejemplo, esas colecciones seguirían conteniendo su contenido.

Por supuesto, si esta fuera la situación, lo llamaría un diseño roto. Solo estoy señalando (de forma pedante, supongo) que la Dispose()operación podría no ser completamente redundante, dependiendo de si hay otros usos de la misma List<>o Dictionary<>no. Se muestra en el fragmento.



Primero de definición. Para mí, recurso no administrado significa alguna clase, que implementa una interfaz IDisposable o algo creado con el uso de llamadas a DLL. GC no sabe cómo tratar con tales objetos. Si la clase tiene, por ejemplo, solo tipos de valor, entonces no considero a esta clase como una clase con recursos no administrados. Para mi código sigo las siguientes prácticas:

  1. Si la clase creada por mí usa algunos recursos no administrados, significa que también debería implementar una interfaz IDisposable para limpiar la memoria.
  2. Limpie los objetos tan pronto como termine de usarlos.
  3. En mi método de disposición, itero sobre todos los miembros IDisposibles de la clase y llamo a Disposición.
  4. En mi método Dispose, llame a GC.SuppressFinalize (this) para notificar al recolector de basura que mi objeto ya se ha limpiado. Lo hago porque llamar a GC es una operación costosa.
  5. Como precaución adicional, trato de hacer posible la llamada de Dispose () varias veces.
  6. En algún momento agrego un miembro privado _disposed y compruebo las llamadas a métodos que el objeto fue limpiado. Y si se limpió, genere ObjectDisposedException
    siguiente plantilla demuestra lo que describí en palabras como ejemplo de código:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

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

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

IDisposable Es bueno para darse de baja de eventos.


Escenarios que utilizo IDisposable: limpiar recursos no administrados, cancelar la suscripción a eventos, cerrar conexiones

El idioma que uso para implementar IDisposable ( no threadsafe ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

Además de su uso principal como una forma de controlar la vida útil de los recursos del sistema (¡cubierto por la asombrosa respuesta de Ian , kudos!), El combo IDisposable / using también se puede usar para determinar el cambio de estado de los recursos globales (críticos) : La consola , los subprocesos , el proceso , cualquier objeto global como una instancia de aplicación .

He escrito un artículo sobre este patrón: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Ilustra cómo puede proteger un estado global de uso frecuente de una manera reutilizable y legible : colores de consola , cultura de subprocesos actual , propiedades de objeto de aplicación de Excel ...






c# .net garbage-collection idisposable