pattern c# .net garbage-collection dispose

c# - pattern - ¿Necesita deshacerse de los objetos y establecerlos en nulo?



dispose pattern c# (12)

¿Necesita deshacerse de los objetos y establecerlos en nulo, o el recolector de basura los limpiará cuando estén fuera del alcance?


Como han dicho otros, definitivamente desea llamar a Dispose si la clase implementa IDisposable . Tomo una posición bastante rígida sobre esto. Algunos pueden afirmar que llamar a Dispose on DataSet , por ejemplo, no tiene sentido porque lo desmontaron y vieron que no hizo nada significativo. Pero, creo que hay falacias abundan en ese argumento.

Lea this para un debate interesante por personas respetadas sobre el tema. Luego lea mi razonamiento here por qué creo que Jeffery Richter está en el campamento equivocado.

Ahora, en si debe o no establecer una referencia a null . La respuesta es no. Permítanme ilustrar mi punto con el siguiente código.

public static void Main() { Object a = new Object(); Console.WriteLine("object created"); DoSomething(a); Console.WriteLine("object used"); a = null; Console.WriteLine("reference set to null"); }

Entonces, ¿cuándo cree que el objeto al que hace referencia a cliente es elegible para la recolección? Si dijiste después de la llamada a a = null , estás equivocado. Si dijiste después de que se completara el método Main , entonces también estás equivocado. La respuesta correcta es que es elegible para la recolección en algún momento durante la llamada a DoSomething . Eso es correcto. Es elegible antes de que la referencia se establezca en null y quizás incluso antes de que se DoSomething la llamada a DoSomething . Esto se debe a que el compilador JIT puede reconocer cuando las referencias de objetos ya no están referenciadas, incluso si todavía están enraizadas.


Cuando un objeto se implementa como IDisposable , debe llamar a Dispose (o Close , en algunos casos, se llamará a Dispose por usted).

Normalmente, no es necesario que establezca objetos en null , ya que el GC sabrá que un objeto ya no se utilizará.

Hay una excepción cuando establezco objetos en null . Cuando recupero muchos objetos (de la base de datos) en los que necesito trabajar, y los almaceno en una colección (o matriz). Cuando el "trabajo" está terminado, configuro el objeto en null , porque el GC no sabe que he terminado de trabajar con él.

Ejemplo:

using (var db = GetDatabase()) { // Retrieves array of keys var keys = db.GetRecords(mySelection); for(int i = 0; i < keys.Length; i++) { var record = db.GetRecord(keys[i]); record.DoWork(); keys[i] = null; // GC can dispose of key now // The record had gone out of scope automatically, // and does not need any special treatment } } // end using => db.Dispose is called


Estoy de acuerdo con la respuesta común de que sí, debería eliminar y no, por lo general no debería establecer la variable en nula ... pero quería señalar que la eliminación NO se trata principalmente de la gestión de la memoria. Sí, puede ayudar (y algunas veces lo hace) con la administración de la memoria, pero su propósito principal es brindarle la liberación determinista de recursos escasos.

Por ejemplo, si abre un puerto de hardware (serie, por ejemplo), un socket TCP / IP, un archivo (en modo de acceso exclusivo) o incluso una conexión de base de datos, ahora ha impedido que cualquier otro código use esos elementos hasta que se liberen. Dispose generalmente publica estos elementos (junto con GDI y otras manijas de "os", etc., de las cuales hay miles disponibles, pero aún están limitadas en general). Si no llama a dipose en el objeto propietario y libera explícitamente estos recursos, intente abrir el mismo recurso en el futuro (u otro programa lo hace), el intento de abrir fallará porque su objeto no recolectado y sin desatar aún tiene el elemento abierto. . Por supuesto, cuando el GC recopila el elemento (si el patrón de Disposición se ha implementado correctamente), el recurso se liberará ... pero no sabe cuándo será eso, por lo que no sabe cuándo es seguro volver a hacerlo. abrir ese recurso Este es el principal problema de Dispose trabaja alrededor. Por supuesto, soltar estos controles a menudo también libera memoria, y nunca soltarlos puede que nunca liberen esa memoria ... por lo tanto, todo lo que se habla sobre fugas de memoria o demoras en la limpieza de la memoria.

He visto ejemplos reales de esto causando problemas. Por ejemplo, he visto aplicaciones web ASP.Net que eventualmente no se pueden conectar a la base de datos (aunque sea por períodos cortos de tiempo, o hasta que se reinicie el proceso del servidor web) porque el grupo de conexiones del servidor SQL está lleno ''... es decir, , se han creado tantas conexiones y no se han liberado explícitamente en un período de tiempo tan corto que no se pueden crear nuevas conexiones y muchas de las conexiones en el grupo, aunque no están activas, siguen siendo referenciadas por objetos no pegados y no recolectados, por lo que pueden t ser reutilizado La eliminación correcta de las conexiones de la base de datos donde sea necesario garantiza que este problema no ocurra (al menos no a menos que tenga un acceso simultáneo muy alto).



Los objetos nunca salen del alcance en C # como lo hacen en C ++. El recolector de basura los trata automáticamente cuando ya no se usan. Este es un enfoque más complicado que C ++, donde el alcance de una variable es completamente determinista. El recolector de basura CLR recorre activamente todos los objetos que se han creado y funciona si se están utilizando.

Un objeto puede quedar "fuera de alcance" en una función, pero si se devuelve su valor, GC vería si la función de llamada mantiene o no el valor de retorno.

Establecer referencias a objetos en null es necesario, ya que la recolección de basura funciona al determinar qué objetos son referenciados por otros objetos.

En la práctica, no tienes que preocuparte por la destrucción, simplemente funciona y es genial :)

Dispose debe llamarse en todos los objetos que implementan IDisposable cuando haya terminado de trabajar con ellos. Normalmente usarías un bloque de using con esos objetos como los siguientes:

using (var ms = new MemoryStream()) { //... }

EDITAR en ámbito de aplicación variable. Craig ha preguntado si el alcance de la variable tiene algún efecto en la vida útil del objeto. Para explicar correctamente ese aspecto de CLR, necesitaré explicar algunos conceptos de C ++ y C #.

Alcance variable real

En ambos idiomas, la variable solo se puede utilizar en el mismo ámbito que se definió: clase, función o un bloque de instrucciones entre llaves. La diferencia sutil, sin embargo, es que en C #, las variables no se pueden redefinir en un bloque anidado.

En C ++, esto es perfectamente legal:

int iVal = 8; //iVal == 8 if (iVal == 8){ int iVal = 5; //iVal == 5 } //iVal == 8

En C #, sin embargo obtienes un error de compilación:

int iVal = 8; if(iVal == 8) { int iVal = 5; //error CS0136: A local variable named ''iVal'' cannot be declared in this scope because it would give a different meaning to ''iVal'', which is already used in a ''parent or current'' scope to denote something else }

Esto tiene sentido si observa el MSIL generado: todas las variables utilizadas por la función se definen al inicio de la función. Echa un vistazo a esta función:

public static void Scope() { int iVal = 8; if(iVal == 8) { int iVal2 = 5; } }

A continuación se muestra la IL generada. Tenga en cuenta que iVal2, que se define dentro del bloque if, en realidad se define a nivel de función. Efectivamente, esto significa que C # solo tiene un alcance de nivel de función y clase en lo que se refiere a la vida útil variable.

.method public hidebysig static void Scope() cil managed { // Code size 19 (0x13) .maxstack 2 .locals init ([0] int32 iVal, [1] int32 iVal2, [2] bool CS$4$0000) //Function IL - omitted } // end of method Test2::Scope

Alcance de C ++ y duración del objeto.

Cada vez que una variable de C ++, asignada en la pila, queda fuera del alcance, se destruye. Recuerde que en C ++ puede crear objetos en la pila o en el montón. Cuando los creas en la pila, una vez que la ejecución abandona el ámbito, se extraen de la pila y se destruyen.

if (true) { MyClass stackObj; //created on the stack MyClass heapObj = new MyClass(); //created on the heap obj.doSomething(); } //<-- stackObj is destroyed //heapObj still lives

Cuando los objetos de C ++ se crean en el montón, deben destruirse explícitamente, de lo contrario es una pérdida de memoria. Sin embargo, no hay tal problema con las variables de pila.

Vida útil del objeto C #

En CLR, los objetos (es decir, los tipos de referencia) siempre se crean en el montón administrado. Esto se refuerza aún más con la sintaxis de creación de objetos. Considere este fragmento de código.

MyClass stackObj;

En C ++, esto crearía una instancia en MyClass en la pila y llamaría a su constructor predeterminado. En C # crearía una referencia a la clase MyClass que no apunta a nada. La única forma de crear una instancia de una clase es usando un new operador:

MyClass stackObj = new MyClass();

En cierto modo, los objetos de C # son muy parecidos a los objetos que se crean utilizando una new sintaxis en C ++: se crean en el montón, pero a diferencia de los objetos de C ++, son gestionados por el tiempo de ejecución, por lo que no tiene que preocuparse por destruirlos.

Dado que los objetos están siempre en el montón, el hecho de que las referencias a objetos (es decir, los punteros) queden fuera de alcance se vuelve discutible. Hay más factores que intervienen en la determinación de si se debe recopilar un objeto que la simple presencia de referencias al objeto.

C # referencias de objetos

Jon Skeet comparó las referencias de objetos en Java con piezas de cadena que se adjuntan al globo, que es el objeto. La misma analogía se aplica a las referencias de objetos C #. Simplemente apuntan a una ubicación del montón que contiene el objeto. Por lo tanto, establecerlo en nulo no tiene ningún efecto inmediato en la vida útil del objeto, el globo continúa existiendo, hasta que el GC lo "apaga".

Continuando con la analogía del globo, parece lógico que una vez que el globo no tenga cuerdas unidas, pueda ser destruido. De hecho, esto es exactamente cómo funcionan los objetos contados de referencia en idiomas no administrados. Excepto que este enfoque no funciona muy bien para referencias circulares. Imagina dos globos que están unidos por una cadena, pero ninguno de ellos tiene una cadena para nada más. Bajo simples reglas de recuento de ref, ambos continúan existiendo, a pesar de que todo el grupo de globos está "huérfano".

Los objetos .NET son muy parecidos a los globos de helio debajo de un techo. Cuando se abre el techo (se ejecuta GC), los globos no utilizados se alejan flotando, aunque podría haber grupos de globos atados entre sí.

.NET GC utiliza una combinación de GC y marca generacionales y barrido. El enfoque generacional implica que el tiempo de ejecución favorece la inspección de los objetos que se han asignado más recientemente, ya que es más probable que no se usen, y el marcado y barrido implica que el tiempo de ejecución recorra todo el gráfico de objetos y se resuelva si hay grupos de objetos que no se utilizan. Esto aborda adecuadamente el problema de la dependencia circular.

Además, .NET GC se ejecuta en otro subproceso (denominado subproceso finalizador), ya que tiene bastante que hacer y hacerlo en el subproceso principal interrumpiría su programa.


Los objetos se limpiarán cuando ya no se usen y cuando el recolector de basura considere oportuno. A veces, es posible que deba establecer un objeto en null para que quede fuera del alcance (como un campo estático cuyo valor ya no necesita), pero en general no es necesario establecerlo en null .

En cuanto a la disposición de objetos, estoy de acuerdo con @Andre. Si el objeto es IDisposable , es una buena idea desecharlo cuando ya no lo necesite, especialmente si el objeto utiliza recursos no administrados. No disponer de recursos no administrados dará lugar a fugas de memoria .

Puede usar la declaración de using para eliminar automáticamente un objeto una vez que su programa abandone el alcance de la declaración de using .

using (MyIDisposableObject obj = new MyIDisposableObject()) { // use the object here } // the object is disposed here

Que es funcionalmente equivalente a:

MyIDisposableObject obj; try { obj = new MyIDisposableObject(); } finally { if (obj != null) { ((IDisposable)obj).Dispose(); } }


Normalmente, no es necesario establecer campos en nulo. Sin embargo, siempre recomiendo disponer de recursos no administrados.

Por experiencia también te aconsejo que hagas lo siguiente:

  • Darse de baja de eventos si ya no los necesita.
  • Establezca cualquier campo que contenga un delegado o una expresión en nulo si ya no es necesario.

He encontrado algunos problemas muy difíciles de encontrar que fueron el resultado directo de no seguir los consejos anteriores.

Un buen lugar para hacer esto es en Dispose (), pero antes es mejor.

En general, si existe una referencia a un objeto, el recolector de basura (GC) puede tardar un par de generaciones más en darse cuenta de que un objeto ya no está en uso. Todo el tiempo el objeto permanece en la memoria.

Puede que no sea un problema hasta que descubra que su aplicación está usando mucha más memoria de la que esperaba. Cuando eso suceda, conecte un generador de perfiles de memoria para ver qué objetos no se están limpiando. Establecer campos que hagan referencia a otros objetos como nulos y eliminar colecciones en disposición puede ayudar realmente al GC a determinar qué objetos puede eliminar de la memoria. El GC recuperará la memoria usada más rápido, lo que hará que su aplicación consuma menos memoria y sea más rápida.


Nunca necesita establecer objetos en nulo en C #. El compilador y el tiempo de ejecución se ocuparán de averiguar cuándo ya no están en el alcance.

Sí, debes desechar los objetos que implementen IDisposable.


Si el objeto se implementa como IDisposable , entonces sí, debe eliminarlo. El objeto podría estar sujeto a recursos nativos (manejadores de archivos, objetos del sistema operativo) que de lo contrario no podrían liberarse de forma inmediata. Esto puede provocar la falta de recursos, problemas de bloqueo de archivos y otros errores sutiles que de otro modo podrían evitarse.

Consulte también Implementación de un método de disposición en MSDN.


Si implementan la interfaz IDisposable, debe disponer de ellos. El recolector de basura se encargará del resto.

EDITAR: lo mejor es usar el comando de using cuando se trabaja con artículos desechables:

using(var con = new SqlConnection("..")){ ...


Siempre llame a disposición. No vale la pena el riesgo. Las grandes aplicaciones empresariales administradas deben ser tratadas con respeto. No se pueden hacer suposiciones o de lo contrario volverá a morderte.

No escuches a leppie.

Muchos objetos no implementan IDisposable, por lo que no tiene que preocuparse por ellos. Si realmente quedan fuera del alcance, serán liberados automáticamente. Además, nunca me he topado con la situación en la que he tenido que establecer algo en nulo.

Una cosa que puede suceder es que muchos objetos se pueden mantener abiertos. Esto puede aumentar considerablemente el uso de memoria de su aplicación. A veces es difícil averiguar si esto es realmente una pérdida de memoria, o si su aplicación está haciendo muchas cosas.

Las herramientas de perfil de memoria pueden ayudar con este tipo de cosas, pero pueden ser difíciles.

Además, siempre cancele la suscripción de eventos que no son necesarios. También tenga cuidado con el enlace y los controles de WPF. No es una situación habitual, pero me encontré con una situación en la que tenía un control WPF que estaba vinculado a un objeto subyacente. El objeto subyacente era grande y ocupaba una gran cantidad de memoria. El control de WPF estaba siendo reemplazado por una nueva instancia, y el anterior todavía estaba dando vueltas por alguna razón. Esto causó una gran pérdida de memoria.

En el sitio posterior, el código estaba mal escrito, pero el punto es que usted quiere asegurarse de que las cosas que no se usan queden fuera del alcance. Eso tomó mucho tiempo para encontrarlo con un perfilador de memoria, ya que es difícil saber qué cosas de la memoria son válidas y cuáles no deberían estar allí.


Yo también tengo que responder. El JIT genera tablas junto con el código de su análisis estático de uso variable. Esas entradas de la tabla son las "GC-Roots" en el marco de pila actual. A medida que avanza el puntero de instrucciones, esas entradas de la tabla se vuelven inválidas y están listas para la recolección de basura. Por lo tanto: si es una variable con ámbito, no necesita establecerla en nula: el GC recopilará el objeto. Si es un miembro o una variable estática, debe establecerlo en nulo