Uso adecuado de la interfaz IDisposable

Al leer la documentación de MSDN, sé que el uso “primario” de la interfaz IDisposable es para limpiar los recursos no administrados.

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

Por ejemplo:

 public class MyCollection : IDisposable { private List _theList = new List(); private Dictionary _theDict = new Dictionary(); // 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 de recolector de basura utilizada por MyCollection sea ​​más rápida de lo normal?

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

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

Nota : ¿Qué es un recurso no administrado ? Si lo encontró en Microsoft .NET Framework: está administrado. Si fuiste a hurgar en MSDN, no está administrado. Cualquier cosa que hayas usado P / Invoca llamadas para salir del agradable y cómodo mundo de todo lo que está disponible para ti en .NET Framwork no es administrado, y ahora eres responsable de limpiarlo.

El objeto que ha creado necesita exponer algún método, que el mundo exterior pueda llamar, para limpiar recursos no administrados. El método se puede nombrar como prefiera:

 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é pasa si su objeto ha asignado un System.Drawing.Bitmap de 250MB (es decir, la clase de bitmap administrado .NET) como un tipo de búfer de cuadros? Claro, este es un objeto .NET administrado, y el recolector de basura lo liberará. Pero, ¿de verdad quieres dejar 250MB de memoria ahí sentado, esperando que el recolector de basura eventualmente venga y lo libere? ¿Qué pasa si hay una conexión de base de datos abierta ? Seguramente no queremos que esa conexión se abra, esperando que el GC 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 desperdicios de mapas de bits y conexiones de bases de datos?

Entonces ahora vamos a:

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

Dispose() nuestro método Dispose() para deshacerse de esos objetos gestionados:

 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é pasa si la persona olvidó llamar a Dispose() en su objeto? ¡Entonces perderían algunos recursos no administrados !

Nota: No perderán recursos administrados , ya que con el tiempo el recostackdor de elementos no utilizados se ejecutará en una secuencia de fondo y liberará la memoria asociada con los objetos no utilizados. Esto incluirá su objeto y cualquier objeto administrado que utilice (por ejemplo, el Bitmap y la DbConnection ).

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

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

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

Nota: En C #, no anula explícitamente el método Finalize() . Usted escribe un método que se parece a un destructor de C ++ , y el comstackdor lo toma como su implementación del método Finalize() :

 ~MyObject() { //we're being finalized (ie 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 el que se destruyen dos objetos. Es muy posible que en el código Dispose() , el objeto administrado del 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 (porque es posible que ya no estén allí ), mientras libera recursos no administrados.

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

Este método interno podría tener 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() a:

 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 se olvide de llamar a su base Dispose cuando anule Dispose:

 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 aparezca el recolector de basura y llame a Finalize, llamará a Dispose nuevamente.

No solo es un desperdicio, sino que si su objeto tiene referencias basura a objetos que ya eliminó de la última llamada a Dispose() , ¡intentará eliminarlos de nuevo!

Observará en mi código que tuve cuidado de eliminar las referencias a los objetos que he eliminado, por lo que no trato de llamar a Dispose en una referencia de objeto basura. Pero eso no impidió que un error sutil se infiltrara.

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

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

La forma en que arreglas esto es decirle al recolector de basura que no necesita molestarse en finalizar el objeto: sus recursos ya han sido limpiados, y no se necesita más trabajo. Para ello, llame 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 a Dispose() , tenemos:

  • recursos liberados no administrados
  • recursos gestionados liberados

No tiene sentido que el GC ejecute el finalizador; todo está solucionado.

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

La documentación para Object.Finalize dice:

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

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

Realiza tareas definidas por la aplicación asociadas con liberar, liberar o restablecer recursos no administrados.

Entonces, ¿cuál es? ¿Cuál es el lugar para limpiar 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); //AC# destructor automatically calls the destructor of its base class. } 

El problema con eso es que no tienes idea de cuándo el recolector de basura se ocupará de finalizar tu objeto. Sus recursos nativos no administrados, no necesarios y no utilizados se mantendrán hasta que finalmente se ejecute el recolector de elementos no utilizados. Luego llamará a su método de finalizador; limpiando recursos no administrados. 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 determinística de los recursos para las instancias de su clase, implemente un método Close o proporcione una implementación IDisposable.Dispose .

Esta es la virtud de usar Dispose para limpiar 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 cuándo el GC decide hacerlo? Tengo un software de reconocimiento facial que ahora necesita deshacerse de 530 MB de imágenes internas, ya que ya no son necesarias. Cuando no lo hacemos: la máquina se detiene bruscamente.

Lectura de bonificación

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

  • Enlace directo: Capítulo 1 muestra de Pearson Publishing
  • imán: 84bf0b960936d677190a2be355858e80ef7542c0

En 35 páginas explica los problemas del uso de objetos binarios e inventa COM ante tus ojos. Una vez que se da cuenta del porqué de COM, las 300 páginas restantes son obvias y solo detallan la implementación de Microsoft.

Creo que cada progtwigdor que alguna vez 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 Bono

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 no intentarlo .

IDisposable se usa a menudo para explotar la sentencia using y aprovechar una forma fácil de hacer 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."); } } } 

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

Puede echar un vistazo a este artículo para obtener más detalles sobre cómo implementar el patrón Dispose, 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 más importante aquí es el Dispose (bool), que en realidad se ejecuta en dos circunstancias diferentes:

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

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

El objective de IDisposable y el patrón de eliminación no se trata de liberar inmediatamente la memoria. La única vez que una llamada a Dispose realmente tendrá la posibilidad de liberar la memoria inmediatamente es cuando maneja el escenario de eliminación == falso y manipula los recursos no administrados. Para el código administrado, la memoria no se recuperará realmente hasta que el GC ejecute un ciclo de recostackción, sobre el que realmente no tiene control (aparte de llamar a GC.Collect (), que ya he mencionado no es una buena idea).

Su escenario no es realmente válido ya que las cadenas en .NET no usan ningún recurso no controlado y no implementan IDisposable, no hay forma de obligarlos a ser “limpiados”.

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

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

Lo que se reduce a esto es que hay una función a la que puede llamar para poner el sistema en un estado, y hay otra función a la que puede llamar para sacarla 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 que coincidan. Uno construye un estado, el otro lo derriba. Si el estado se ha construido pero no se ha derribado aún, entonces existe una instancia del recurso. Debe organizar el desassembly en el momento correcto: el recurso no está gestionado por el CLR. El único tipo de recurso administrado automáticamente es la memoria. Hay dos tipos: el GC y la stack. Los tipos de valores son gestionados por la stack (o conectando un paseo dentro de los tipos de referencia), y los tipos de referencia son gestionados por el GC.

Estas funciones pueden causar cambios de estado que pueden intercalarse libremente o pueden necesitar estar perfectamente nesteds. Los cambios de estado pueden ser peligrosos o pueden no serlo.

Mire el ejemplo en la pregunta de Justice. Los cambios en la sangría del archivo de registro deben estar perfectamente nesteds, o todo va mal. Además, es poco probable que sean enhebrables.

Es posible enganchar un paseo con el recolector de basura para limpiar sus recursos no administrados. Pero solo si las funciones de cambio de estado son seguras para hilos y dos estados pueden tener vidas que se superponen de alguna manera. ¡Así que el ejemplo de Justice 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 debe usar la instrucción using para tener alguna posibilidad de garantizar que se Dispose . Esto es esencialmente como andar en bicicleta con la stack (por lo que el finalizador es para la GC, using es para la stack).

La parte que falta es que tienes que escribir Dispose manualmente y hacer que llame a tus campos y a tu clase base. Los progtwigdores de C ++ / CLI no tienen que hacer eso. El comstackdor lo escribe para ellos en la mayoría de los casos.

Existe una alternativa, que prefiero para los estados que anidan perfectamente y que no son seguros (aparte de cualquier otra cosa, evitando los recambios descartables, 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 luego un simple ejemplo 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 si creara su propia estructura de control para cumplir el mismo propósito que using , excepto que ya no tiene ningún peligro de que la persona que llama abuse de ella. No hay manera de que no puedan limpiar el recurso.

Esta técnica es menos útil si el recurso es del tipo que puede tener vidas superpuestas, porque entonces se desea poder construir el recurso A, luego el recurso B, luego matar el recurso A y luego matar el recurso B. No se puede hacer eso si has forzado al usuario a anidar perfectamente así. Pero luego necesita usar IDisposable (pero aún sin un finalizador, a menos que haya implementado threadsafety, que no es gratis).

Escenarios Uso de IDisposable: limpiar recursos no administrados, cancelar suscripción para eventos, cerrar conexiones

La expresión idiomática que uso para implementar IDisposable ( no es segura ):

 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 } 

Si MyCollection va a ser basura recolectada de todos modos, entonces no debería necesitar deshacerse de ella. Si lo hace, simplemente agitará la CPU más de lo necesario e incluso puede invalidar algún análisis precalculado que el recolector de basura ya haya realizado.

Uso IDisposable para hacer cosas como asegurar que los hilos 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 stack 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 las páginas que es poco probable que cambien muy a menudo para evitar volver a explorar la página innecesariamente. El GC recibe notificación del kernel cuando los datos en una página cambian, por lo que sabe que la página está sucia y requiere un nuevo análisis. 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. Anecdóticamente, estos ganchos no estaban disponibles en Mac OS X para el equipo que portó el GC a Mac para poder utilizar el complemento de Silverlight 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. Imagine también que el proceso ha estado funcionando por un tiempo. Lo más probable es que muchas de las páginas de memoria de ese proceso se hayan intercambiado en el 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 está descargando intercambie todos esos datos y páginas de códigos en la memoria para ‘liberar’ los recursos que el sistema operativo va a liberar cuando el proceso finaliza. Esto se aplica a recursos administrados e incluso ciertos no administrados. Solo se deben eliminar los recursos que mantienen vivos los subprocesos que no son de fondo, de lo contrario el proceso permanecerá activo.

Ahora, durante la ejecución normal, hay recursos efímeros que deben limpiarse correctamente (como @fezmonkey señala las conexiones de la base de datos, los sockets, los controles de las ventanas ) para evitar memory leaks no administradas. Estas son las clases de cosas que tienen que ser eliminadas. Si creas una clase que posee un hilo (y por propiedad quiero decir que lo creó y por lo tanto es responsable de asegurar que se detenga, al menos por mi estilo de encoding), entonces esa clase probablemente debe implementar IDisposable y derribar el hilo durante Dispose

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

Yep, that code is completely redundant and unnecessary and it doesn’t make the garbage collector do anything it wouldn’t otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. 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 

It’s functionally identical to this for purposes of memory management:

 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 

If you really really really need to free the memory this very instant, call GC.Collect() . There’s no reason to do this here, though. The memory will be freed when it’s needed.

If you want to delete right now , use unmanaged memory .

Ver:

  • Marshal.AllocHGlobal
  • Marshal.FreeHGlobal
  • Marshal.DestroyStructure

I won’t repeat the usual stuff about Using or freeing un-managed resources, that has all been covered. But I would like to point out what seems a common misconception.
Given the following code

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

I realise that the Disposable implementation does not follow current guidelines, but hopefully you all get the idea.
Now, when Dispose is called, how much memory gets freed?

Answer: None.
Calling Dispose can release unmanaged resources, it CANNOT reclaim managed memory, only the GC can do that. Thats not to say that the above isn’t a good idea, following the above pattern is still a good idea in fact. Once Dispose has been run, there is nothing stopping the GC re-claiming the memory that was being used by _Large, even though the instance of LargeStuff may still be in scope. The strings in _Large may also be in gen 0 but the instance of LargeStuff might be gen 2, so again, memory would be re-claimed sooner.
There is no point in adding a finaliser to call the Dispose method shown above though. That will just DELAY the re-claiming of memory to allow the finaliser to run.

In the example you posted, it still doesn’t “free the memory now”. All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You’d have to run some tests to be sure.


The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit call never happened it would then call Rollback on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

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

I’ve also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

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

So here are a couple of concrete examples that don’t do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.

If anything, I’d expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn’t do that if the Dispose didn’t do it…

Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I’ve written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties

There are things that the Dispose() operation does in the example code that might have an effect that would not occur due to a normal GC of the MyCollection object.

If the objects referenced by _theList or _theDict are referred to by other objects, then that List<> or Dictionary<> object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.

Of course, if this were the situation I would call it a broken design – I’m just pointing out (pedantically, I suppose) that the Dispose() operation might not be completely redundant, depending on whether there are other uses of the List<> or Dictionary<> that are not shown in the fragment.

One problem with most discussions of “unmanaged resources” is that they don’t really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn’t helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside ‘thing’ to do something on its behalf, to the detriment of some other ‘things’, and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside ‘thing’ that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the ‘thing’s usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside ‘thing’ to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside ‘thing’ if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.

IDisposable is good for unsubscribing from events.

First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn’t know how to deal with such objects. If class has for example only value types, then I don’t consider this class as class with unmanaged resources. For my code I follow next practices:

  1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
  2. Clean objects as soon as I finished usage of it.
  3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
  4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
  5. As additional precaution I try to make possible calling of Dispose() multiple times.
  6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
    Following template demonstrates what I described in words as sample of code:

 public class SomeClass : IDisposable { ///  /// As usually I don't care was object disposed or not ///  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; } } 

The most justifiable use case for disposal of managed resources, is preparation for the GC to reclaim resources that would otherwise never be collected.

A prime example is circular references.

Whilst it’s best practice to use patterns that avoid circular references, if you do end up with (for example) a ‘child’ object that has a reference back to its ‘parent’, this can stop GC collection of the parent if you just abandon the reference and rely on GC – plus if you have implemented a finalizer, it’ll never be called.

The only way round this is to manually break the circular references by setting the Parent references to null on the children.

Implementing IDisposable on parent and children is the best way to do this. When Dispose is called on the Parent, call Dispose on all Children, and in the child Dispose method, set the Parent references to null.

Your given code sample is not a good example for IDisposable usage. Dictionary clearing normally shouldn’t go to the Dispose method. Dictionary items will be cleared and disposed when it goes out of scope. IDisposable implementation is required to free some memory/handlers that will not release/free even after they out of scope.

The following example shows a good example for IDisposable pattern with some code and comments.

 public class DisposeExample { // A base class that implements IDisposable. // By implementing IDisposable, you are announcing that // instances of this type allocate scarce resources. public class MyResource: IDisposable { // Pointer to an external unmanaged resource. private IntPtr handle; // Other managed resource this class uses. private Component component = new Component(); // Track whether Dispose has been called. private bool disposed = false; // The class constructor. public MyResource(IntPtr handle) { this.handle = handle; } // 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. protected virtual 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. component.Dispose(); } // Call the appropriate methods to clean up // unmanaged resources here. // If disposing is false, // only the following code is executed. CloseHandle(handle); handle = IntPtr.Zero; // Note disposing has been done. disposed = true; } } // Use interop to call the method necessary // to clean up the unmanaged resource. [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); // 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. ~MyResource() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. Dispose(false); } } public static void Main() { // Insert code here to create // and use the MyResource object. } }