¿Necesita deshacerse de los objetos y ponerlos en nulo, o el recolector de basura los limpiará cuando salgan del alcance?
310
¿Necesita deshacerse de los objetos y ponerlos en nulo, o el recolector de basura los limpiará cuando salgan del alcance?
Dispose()
método! Esta es una variación sutil en esta pregunta, pero importante porque el objeto que se está eliminando no puede saber si está "fuera de alcance" (llamarDispose()
no es una garantía). Más aquí: stackoverflow.com/questions/6757048/…Respuestas:
Los objetos se limpiarán cuando ya no se usen y cuando el recolector de basura lo considere adecuado. A veces, es posible que necesite establecer un objeto para
null
que quede fuera del alcance (como un campo estático cuyo valor ya no necesita), pero en general generalmente no es necesario configurarlonull
.En cuanto a la eliminación de objetos, estoy de acuerdo con @Andre. Si el objeto es
IDisposable
, es una buena idea deshacerse de él cuando ya no lo necesite, especialmente si el objeto utiliza recursos no administrados. No disponer de recursos no administrados dará lugar a pérdidas de memoria .Puede usar la
using
declaración para disponer automáticamente un objeto una vez que su programa abandone el alcance de lausing
declaración.Que es funcionalmente equivalente a:
fuente
if (obj != null) ((IDisposable)obj).Dispose();
IDisposable
. No eliminar un objeto generalmente no provocará una pérdida de memoria en ninguna clase bien diseñada. Al trabajar con recursos no administrados en C #, debe tener un finalizador que aún libere los recursos no administrados. Esto significa que, en lugar de desasignar los recursos cuando debería hacerse, se diferirá cuando el recolector de basura finalice el objeto administrado. Sin embargo, aún puede causar muchos otros problemas (como bloqueos inéditos). ¡Deberías descartar unIDisposable
pensamiento!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 revisa 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, entonces GC vería si la función de llamada mantiene o no el valor de retorno.
No
null
es necesario establecer referencias de objeto ya que la recolección de basura funciona al determinar a qué objetos hacen referencia otros objetos.En la práctica, no tienes que preocuparte por la destrucción, simplemente funciona y es genial :)
Dispose
debe invocarse en todos los objetos que se implementanIDisposable
cuando termina de trabajar con ellos. Normalmente usarías unusing
bloque con esos objetos así:EDITAR En alcance variable. Craig ha preguntado si el alcance variable tiene algún efecto en la vida útil del objeto. Para explicar adecuadamente ese aspecto de CLR, necesitaré explicar algunos conceptos de C ++ y C #.
Alcance variable real
En ambos idiomas, la variable solo se puede usar en el mismo alcance que se definió: clase, función o un bloque de enunciado encerrado entre llaves. La sutil diferencia, sin embargo, es que en C #, las variables no se pueden redefinir en un bloque anidado.
En C ++, esto es perfectamente legal:
En C #, sin embargo, obtienes un error de compilación:
Esto tiene sentido si observa el MSIL generado: todas las variables utilizadas por la función se definen al comienzo de la función. Echa un vistazo a esta función:
A continuación se muestra la IL generada. Tenga en cuenta que iVal2, que se define dentro del bloque if, se define realmente a nivel de función. Efectivamente, esto significa que C # solo tiene un alcance de nivel de clase y función en lo que respecta a la vida útil de la variable.
Alcance de C ++ y vida útil del objeto
Cada vez que una variable de C ++, asignada en la pila, queda fuera de alcance, se destruye. Recuerde que en C ++ puede crear objetos en la pila o en el montón. Cuando los crea en la pila, una vez que la ejecución abandona el alcance, se sacan de la pila y se destruyen.
Cuando se crean objetos C ++ en el montón, deben destruirse explícitamente; de lo contrario, se trata de una pérdida de memoria. Sin embargo, no existe tal problema con las variables de pila.
C # Objeto de por vida
En CLR, los objetos (es decir, los tipos de referencia) siempre se crean en el montón administrado. Esto se ve reforzado por la sintaxis de creación de objetos. Considere este fragmento de código.
En C ++ esto crearía una instancia
MyClass
en la pila y llamaría a su constructor predeterminado. En C # crearía una referencia a la claseMyClass
que no apunta a nada. La única forma de crear una instancia de una clase es utilizando elnew
operador:En cierto modo, los objetos de C # son muy parecidos a los objetos que se crean utilizando la
new
sintaxis en C ++: se crean en el montón pero, a diferencia de los objetos de C ++, son administrados por el tiempo de ejecución, por lo que no tiene que preocuparse por destruirlos.Dado que los objetos siempre están en el montón, el hecho de que las referencias a objetos (es decir, punteros) se salgan del alcance se vuelve discutible. Hay más factores involucrados en determinar si un objeto se va a recolectar que simplemente la presencia de referencias al objeto.
C # referencias de objeto
Jon Skeet comparó las referencias de objetos en Java con trozos de cuerda que están unidos al globo, que es el objeto. La misma analogía se aplica a las referencias de objetos de C #. Simplemente apuntan a una ubicación del montón que contiene el objeto. Por lo tanto, establecerlo como nulo no tiene un efecto inmediato en la vida útil del objeto, el globo continúa existiendo, hasta que el GC lo "explota".
Continuando con la analogía del globo, parecería lógico que una vez que el globo no tiene ataduras, puede ser destruido. De hecho, así es exactamente cómo funcionan los objetos contados de referencia en lenguajes no administrados. Excepto que este enfoque no funciona para referencias circulares muy bien. Imagine dos globos que están unidos por una cuerda pero ninguno de los globos tiene una cuerda para nada más. Bajo simples reglas de conteo de referencias, 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 bajo un techo. Cuando se abre el techo (el GC funciona): los globos no utilizados se alejan flotando, a pesar de que puede haber grupos de globos unidos entre sí.
.NET GC utiliza una combinación de GC generacional y marca y barrido. El enfoque generacional implica el tiempo de ejecución que 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 marcar y barrer implica el tiempo de ejecución que recorre todo el gráfico de objetos y funciona si hay grupos de objetos que no se utilizan. Esto trata adecuadamente con el problema de dependencia circular.
Además, .NET GC se ejecuta en otro subproceso (llamado subproceso finalizador) ya que tiene bastante que hacer y hacerlo en el subproceso principal interrumpiría su programa.
fuente
Como otros han dicho, definitivamente quieres llamar
Dispose
si la clase se implementaIDisposable
. Tomo una posición bastante rígida sobre esto. Algunos afirman que poder llamarDispose
enDataSet
, por ejemplo, no tiene sentido, ya que la desmontaron y vi que no hizo nada significativo. Pero, creo que abundan las falacias en ese argumento.Lea esto para un debate interesante de personas respetadas sobre el tema. Entonces lea mi razonamiento aquí por qué creo que Jeffery Richter está en el campamento equivocado.
Ahora, sobre si debe establecer una referencia o no
null
. La respuesta es no. Permítanme ilustrar mi punto con el siguiente código.Entonces, ¿cuándo crees que el objeto al que hace referencia
a
es elegible para la colección? Si dijo después de la llamada,a = null
entonces está equivocado. Si dijo después de que elMain
método se completa, también está equivocado. La respuesta correcta es que es elegible para la recolección en algún momento durante la llamada aDoSomething
. Eso es correcto. Es elegible antes de que se establezca la referencianull
y quizás incluso antes de queDoSomething
finalice la llamada . Esto se debe a que el compilador JIT puede reconocer cuándo las referencias a objetos ya no se desreferencian, incluso si aún están enraizadas.fuente
a
es un campo de miembro privado en una clase? Sia
no se establece como nulo, el GC no tiene forma de saber sia
se usará nuevamente en algún método, ¿verdad? Pora
lo tanto , no se recopilará hasta que se recopile toda la clase que contiene. ¿No?a
fuera un miembro de la clase y la clase que contienea
todavía estuviera rooteada y en uso, entonces también se quedaría. Ese es un escenario en el que configurarlonull
podría ser beneficioso.Dispose
es importante: no es posible invocarDispose
(o cualquier otro método no en línea) en un objeto sin una referencia arraigada a él; llamarDispose
después de que uno haya terminado de usar un objeto asegurará que una referencia arraigada continuará existiendo durante la última acción realizada en él. Abandonar todas las referencias a un objeto sin invocarDispose
, irónicamente, puede tener el efecto de ocasionar que los recursos del objeto se liberen ocasionalmente demasiado pronto .Nunca necesita establecer objetos en nulo en C #. El compilador y el tiempo de ejecución se encargarán de averiguar cuándo ya no están dentro del alcance.
Sí, debe deshacerse de los objetos que implementan IDisposable.
fuente
want
debe anularlo tan pronto como haya terminado con él para que sea libre de ser reclamado.Estoy de acuerdo con la respuesta común aquí que sí, debe deshacerse y no, generalmente no debe establecer la variable en nula ... pero quería señalar que deshacerse NO se trata principalmente de la administración de memoria. Sí, puede ayudar (y a veces lo hace) con la administración de la memoria, pero su propósito principal es brindarle una liberación determinista de los escasos recursos.
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 evitado que cualquier otro código use esos elementos hasta que se liberen. Dispose generalmente libera estos elementos (junto con GDI y otros identificadores "os", etc., que hay 1000 disponibles, pero en general son limitados). Si no llama a dipose en el objeto propietario y libera explícitamente estos recursos, intente abrir el mismo recurso nuevamente en el futuro (u otro programa lo hace), ese intento de apertura fallará porque su objeto no expuesto y no recolectado todavía tiene el elemento abierto . Por supuesto, cuando el GC recolecta el elemento (si el patrón Dispose se ha implementado correctamente), el recurso se liberará ... pero no sabe cuándo será, así que no No sé cuándo es seguro volver a abrir ese recurso. Este es el problema principal con el que Dispose funciona. Por supuesto, la liberación de estos controladores a menudo también libera memoria, y nunca liberarlos nunca puede liberar esa memoria ... de ahí toda la charla sobre fugas de memoria o retrasos en la limpieza de la memoria.
He visto ejemplos del mundo real de esto causando problemas. Por ejemplo, he visto aplicaciones web ASP.Net que eventualmente no se conectan a la base de datos (aunque sea por cortos períodos 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 lanzado 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, todavía están referenciadas por objetos no recogidos y no recogidos, y también pueden ' No se reutilizará. La disposición correcta de las conexiones de la base de datos cuando sea necesario asegura que este problema no ocurra (al menos no a menos que tenga un acceso simultáneo muy alto).
fuente
Si el objeto se implementa
IDisposable
, entonces sí, debe deshacerse de él. El objeto podría estar colgado en recursos nativos (identificadores de archivo, objetos del sistema operativo) que de lo contrario no podrían liberarse de inmediato. Esto puede conducir a la falta de recursos, problemas de bloqueo de archivos y otros errores sutiles que de otro modo podrían evitarse.Consulte también Implementar un método de eliminación en MSDN.
fuente
Dispose
se llame. Además, si su objeto está reteniendo un recurso escaso o está bloqueando algún recurso (por ejemplo, un archivo), entonces querrá liberarlo lo antes posible. Esperar a que el GC haga eso es subóptimo.might
llamar, sinowill
llamar.Si implementan la interfaz IDisposable, entonces debe eliminarlos. El recolector de basura se encargará del resto.
EDITAR: lo mejor es usar el
using
comando cuando se trabaja con elementos desechables:fuente
Cuando se implementa un objeto
IDisposable
, debe llamarDispose
(oClose
, en algunos casos, eso llamará Dispose para usted).Normalmente no tiene que establecer objetos en
null
, porque el GC sabrá que un objeto ya no se utilizará.Hay una excepción cuando configuro 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 se hace el "trabajo", configuro el objeto ennull
, porque el GC no sabe que he terminado de trabajar con él.Ejemplo:
fuente
Normalmente, no hay necesidad de establecer campos en nulo. Sin embargo, siempre recomendaría disponer de recursos no administrados.
Por experiencia, también te aconsejaría que hagas lo siguiente:
Me he encontrado con algunos problemas muy difíciles de encontrar que fueron el resultado directo de no seguir los consejos anteriores.
Un buen lugar para hacerlo es Dispose (), pero antes suele ser 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.
Eso puede no ser un problema hasta que descubra que su aplicación está usando mucha más memoria de la que esperaba. Cuando eso sucede, 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 borrar colecciones al eliminarlas realmente puede ayudar al GC a descubrir qué objetos puede eliminar de la memoria. El GC recuperará la memoria usada más rápido, haciendo que su aplicación tenga menos memoria y sea más rápida.
fuente
Siempre llame a disponer. No vale la pena el riesgo. Las grandes aplicaciones empresariales gestionadas deben tratarse con respeto. No se pueden hacer suposiciones o de lo contrario volverá a morderte.
No escuches leppie.
Muchos objetos en realidad no implementan IDisposable, por lo que no tiene que preocuparse por ellos. Si realmente se salen del alcance, serán liberados automáticamente. Además, nunca me he encontrado 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 determinar si esto es realmente una pérdida de memoria o si su aplicación solo está haciendo muchas cosas.
Las herramientas de perfil de memoria pueden ayudar con cosas como esa, pero puede ser complicado.
Además, siempre cancele la suscripción a eventos que no son necesarios. También tenga cuidado con los enlaces y 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 viejo todavía estaba dando vueltas por alguna razón. Esto causó una gran pérdida de memoria.
En retrospectiva, el código estaba mal escrito, pero el punto es que desea asegurarse de que las cosas que no se usan están fuera de alcance. Se tardó mucho tiempo en encontrarlo con un generador de perfiles de memoria, ya que es difícil saber qué cosas en la memoria son válidas y qué no debería estar allí.
fuente
Tengo que responder también. El JIT genera tablas junto con el código a partir de su análisis estático del uso de variables. Esas entradas de la tabla son las "Raíces GC" en el marco de la 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 se trata de una variable de ámbito, no necesita establecerla como nula; el GC recopilará el objeto. Si es un miembro o una variable estática, debe configurarlo como nulo
fuente