recolector basura c# .net memory memory-management garbage-collection

basura - destructor c#



Como.NET tiene un recolector de basura, ¿por qué necesitamos finalizadores/destructores/disponer de patrón? (12)

  1. Hay cosas que el recolector de basura no puede limpiar después de ti
  2. Incluso con cosas que puede limpiar, puede ayudarlo a limpiarlo antes

Si entiendo correctamente, el tiempo de ejecución de .net siempre se limpiará después de mí. Entonces, si creo objetos nuevos y dejo de hacer referencia a ellos en mi código, el tiempo de ejecución limpiará esos objetos y liberará la memoria que ocuparon.

Dado que este es el caso, ¿por qué algunos objetos necesitan tener un método de destrucción o eliminación? ¿No limpiará el tiempo de ejecución después de ellos cuando ya no se les haga referencia?


El recolector de basura solo se ejecutará si el sistema no está bajo presión de memoria, a menos que realmente necesite liberar algo de memoria. Eso significa que nunca se puede estar seguro de cuándo se ejecutará el GC.

Ahora, imagina que eres una conexión de base de datos. Si deja que el GC limpie después de usted, puede estar conectado a la base de datos durante mucho más tiempo de lo necesario, lo que causa una situación de carga extraña. En ese caso, desea implementar IDisposable, para que el usuario pueda llamar a Dispose () o usar using () para asegurarse realmente de que la conexión se cierre lo antes posible sin tener que depender de GC, que puede ejecutarse mucho más tarde.

En general, IDisposable se implementa en cualquier clase que funcione con recursos no administrados.


El recolector de elementos no utilizados de .NET sabe cómo manejar los objetos administrados dentro del tiempo de ejecución de .NET. Pero el patrón Dispose (IDisposable) se usa principalmente para objetos no administrados que usa una aplicación.

En otras palabras, el tiempo de ejecución de .NET no necesariamente sabe cómo tratar con cada tipo de dispositivo o dispositivo (cierre de conexiones de red, manejadores de archivos, dispositivos gráficos, etc.), por lo que el uso de IDisposable proporciona una forma de decir "déjame" implementar algunas tareas de limpieza "en un tipo. Al ver esa implementación, el recolector de basura puede llamar a Dispose () y asegurarse de que las cosas que están fuera del montón administrado se limpien.


Es posible que algunos objetos necesiten limpiar elementos de bajo nivel. Tales como hardware que necesita ser cerrado, etc.


Hay algunos (muy pocos) casos en los que puede ser necesario realizar una acción específica cuando ya no se usa un objeto gestionado puro, no puedo dar con un ejemplo en la parte superior de mi cabeza, pero he visto un par de usos legítimos a través de los años. Pero la razón principal es limpiar cualquier recurso no administrado que el objeto pueda estar usando.

Por lo tanto, en general, no necesitará usar el patrón Dispose / Finalize a menos que esté utilizando recursos no administrados.


La explicación simplista:

  • Dispose está diseñado para la eliminación determinística de los recursos que no son de memoria, especialmente los recursos escasos . Por ejemplo, un identificador de ventana o una conexión de base de datos.
  • Finalize está diseñado para la eliminación no determinística de recursos que no son de memoria, generalmente como un backstop si no se llamó a Dispose.

Algunas pautas para implementar el método Finalize:

  • Solo implemente Finalize en objetos que requieren finalización, porque hay un costo de rendimiento asociado con los métodos Finalize.
  • Si necesita un método Finalize, considere implementar IDisposable para permitir que los usuarios de su tipo eviten el costo de invocar el método Finalize.
  • Sus métodos de Finalización deben estar protegidos en lugar de públicos.
  • Su método Finalize debe liberar todos los recursos externos que posea el tipo, pero solo aquellos que posee. No debe hacer referencia a ningún otro recurso.
  • CLR no garantiza el orden en que se invocan los métodos de Finalización. Como señala Daniel en su comentario, esto significa que un método Finalize no debería acceder a ningún tipo de referencia miembro, si es posible, porque estos pueden tener (o pueden tener algún día) sus propios finalizadores.
  • Nunca invoque un método Finalize directamente en ningún otro tipo que no sea el tipo base del tipo.
  • Intente evitar cualquier excepción no controlada en su método Finalize, ya que eso terminará su proceso (en 2.0 o superior).
  • Evite realizar cualquier tarea de ejecución prolongada en el método de Finalizer, ya que eso bloqueará la secuencia del Finalizador y evitará que se ejecuten otros métodos de Finalizer.

Algunas pautas para implementar el método Dispose:

  • Implemente el patrón de diseño de disposición en un tipo que encapsula recursos que explícitamente deben liberarse.
  • Implemente el patrón de diseño de disposición en un tipo base que tenga uno o más tipos derivados que se aferren a los recursos, incluso si el tipo de base no lo hace.
  • Una vez que se ha invocado el método Dispose en una instancia, evite que se ejecute el método Finalize llamando al método GC.SuppressFinalize. La única excepción a esta regla es la rara situación en la que se debe trabajar en Finalize que no está cubierto por Dispose.
  • No asuma que se llamará a Dispose. Los recursos no administrados que son propiedad de un tipo también deben ser liberados en un método Finalize en caso de que no se llame a Dispose.
  • Lanza una ObjectDisposedException de métodos de instancia en este tipo (que no sea Dispose) cuando los recursos ya están eliminados. Esta regla no se aplica al método Dispose porque debe llamarse varias veces sin lanzar una excepción.
  • Propague las llamadas a Dispose a través de la jerarquía de tipos base. El método Dispose debe liberar todos los recursos que tenga este objeto y cualquier objeto propiedad de este.
  • Debería considerar no permitir que un objeto sea utilizable después de que se haya invocado su método Dispose. Volver a crear un objeto que ya ha sido eliminado es un patrón difícil de implementar.
  • Permita que un método de eliminación sea llamado más de una vez sin lanzar una excepción. El método no debería hacer nada después de la primera llamada.

La verdadera razón es porque la recolección de basura de .net NO está diseñada para recolectar recursos no administrados , por lo tanto, la limpieza de estos recursos aún recae en las manos del desarrollador. Además, los finalizadores de objetos no se invocan automáticamente cuando el objeto queda fuera del alcance. Son llamados por el GC en algún momento indeterminado. Y cuando se les llama, GC no se ejecuta de inmediato, espera a la siguiente ronda para llamarlo, lo que aumenta el tiempo para limpiar aún más, lo cual no es bueno cuando los objetos tienen pocos recursos no administrados (como archivos). o conexiones de red). Ingrese el patrón desechable, donde el desarrollador puede liberar manualmente recursos escasos en un momento determinado (cuando llama a yourobject.Dispose () o la instrucción using (...)). Tenga en cuenta que debe llamar a GC.SuppressFinalize (esto); en su método de desecho para decirle al GC que el objeto se desechó manualmente y no debe finalizarse. Le sugiero que eche un vistazo al libro de Pautas de diseño del marco por K. Cwalina y B. Abrams. Explica el patrón Desechable muy bueno.

¡Buena suerte!


Las respuestas anteriores son buenas, pero permítanme enfatizar el punto importante aquí una vez más. En particular, dijiste que

Si entiendo correctamente, el tiempo de ejecución de .net siempre se limpiará después de mí.

Esto es parcialmente correcto. De hecho, .NET solo ofrece administración automática para un recurso en particular : la memoria principal. Todos los otros recursos necesitan limpieza manual. 1)

Curiosamente, la memoria principal obtiene un estado especial en casi todas las discusiones sobre los recursos del programa. Por supuesto, hay una buena razón para esto: la memoria principal suele ser el recurso más escaso. Pero vale la pena recordar que también hay otros tipos de recursos que también necesitan administración.

1) El intento habitual de solución es acoplar la vida útil de otros recursos a la duración de las ubicaciones de memoria o identificadores en el código, de ahí la existencia de finalizadores.


Los finalizadores son necesarios para garantizar la liberación de recursos escasos en el sistema, como manejadores de archivos, sockets, kernel, etc. Dado que el finalizador siempre se ejecuta al final de la vida de los objetos, es el lugar designado para liberar esos identificadores.

El patrón Dispose se usa para proporcionar la destrucción determinística de los recursos. Como el recolector de elementos no utilizados en tiempo de ejecución de .net no es determinista (lo que significa que nunca puede estar seguro de cuándo el tiempo de ejecución recogerá los objetos antiguos y llamará a su finalizador), se necesitaba un método para garantizar la liberación determinista de los recursos del sistema. Por lo tanto, cuando implementa el patrón Dispose correctamente, proporciona una liberación determinística de los recursos y, en los casos en que el consumidor es descuidado y no elimina el objeto, el finalizador lo limpiará.

Un ejemplo simple de por qué es necesario Dispose podría ser un método de registro rápido y sucio:

public void Log(string line) { var sw = new StreamWriter(File.Open( "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)); sw.WriteLine(line); // Since we don''t close the stream the FileStream finalizer will do that for // us but we don''t know when that will be and until then the file is locked. }

En el ejemplo anterior, el archivo permanecerá bloqueado hasta que el recolector de basura llame al finalizador en el objeto StreamWriter . Esto presenta un problema ya que, mientras tanto, el método podría llamarse nuevamente para escribir un registro, pero esta vez fallará porque el archivo todavía está bloqueado.

La forma correcta es deshacerse del objeto cuando haya terminado de usarlo:

public void Log(string line) { using (var sw = new StreamWriter(File.Open( "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))) { sw.WriteLine(line); } // Since we use the using block (which conveniently calls Dispose() for us) // the file well be closed at this point. }

Por cierto, técnicamente finalizadores y destructores significan lo mismo; Prefiero llamar finalizadores de c # ''finalizadores'' ya que de lo contrario tienden a confundir a las personas con destructores de C ++, que a diferencia de C #, son deterministas.


Los objetos que necesitan descructors y disponen de métodos utilizan recursos no gestionados. Entonces el recolector de basura no puede limpiar esos recursos, y usted tiene que hacer esto por su cuenta.

Mire la documentación de MSDN para IDisposable; http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

El ejemplo usa un controlador no administrado - IntPr.


Porque el recolector de basura no puede recopilar lo que el entorno administrado no asignó. Por lo tanto, cualquier llamada a una API no administrada que dé como resultado una asignación de memoria debe recopilarse a la manera antigua.


Principalmente para código no administrado e interacción con código no administrado. El código administrado "puro" nunca debería necesitar un finalizador. Desechable por otro lado es solo un patrón útil para forzar que algo sea liberado cuando hayas terminado con él.