Establecer objetos en nulo / nada después de su uso en .NET

187

¿Debería establecer todos los objetos en null( Nothingen VB.NET) una vez que haya terminado con ellos?

Entiendo que en .NET es esencial deshacerse de cualquier instancia de objetos que implementen la IDisposableinterfaz para liberar algunos recursos, aunque el objeto todavía puede ser algo después de ser eliminado (de ahí la isDisposedpropiedad en los formularios), por lo que supongo que aún puede residir en memoria o al menos en parte?

También sé que cuando un objeto se sale del alcance, se marca para su recolección y está listo para la próxima pasada del recolector de basura (aunque esto puede llevar tiempo).

Entonces, con esto en mente, ¿lo configurará para nullacelerar el sistema liberando la memoria, ya que no tiene que resolver que ya no está dentro del alcance y hay algún efecto secundario negativo?

Los artículos de MSDN nunca hacen esto en ejemplos y actualmente lo hago ya que no puedo ver el daño. Sin embargo, me he encontrado con una mezcla de opiniones, por lo que cualquier comentario es útil.

Juan
fuente
44
+1 gran pregunta. ¿Alguien sabe alguna circunstancia bajo la cual el compilador optimizará la tarea por completo? es decir, alguien miró a MSIL en diferentes circunstancias y observó IL para establecer un objeto como nulo (o la falta del mismo)
Tim Medora

Respuestas:

73

Karl es absolutamente correcto, no hay necesidad de establecer objetos en nulo después de su uso. Si un objeto se implementa IDisposable, solo asegúrese de llamar IDisposable.Dispose()cuando haya terminado con ese objeto (envuelto en un try... finallyo un using()bloque). Pero incluso si no recuerda llamar Dispose(), el método finalizador en el objeto debería estar llamando Dispose()por usted.

Pensé que este era un buen trato:

Excavando en IDisposable

y esto

Entendiendo IDisposable

No tiene sentido intentar adivinar el GC y sus estrategias de gestión porque es autoajustable y opaco. Hubo una buena discusión sobre el funcionamiento interno con Jeffrey Richter en Dot Net Rocks aquí: Jeffrey Richter en el Modelo de memoria de Windows y el libro de Richters CLR a través de C # capítulo 20 tiene un gran tratamiento:

Kev
fuente
66
La regla sobre no establecer un valor nulo no es "difícil y rápido" ... si el objeto se coloca en el montón de objetos grandes (tamaño es> 85K), ayudará al GC si configura el objeto como nulo cuando haya terminado usándolo
Scott Dorman
Estoy de acuerdo en un grado limitado, pero a menos que esté comenzando a experimentar presión en la memoria, entonces no veo la necesidad de "optimizar prematuramente" al establecer los objetos en nulos después del uso.
Kev
21
Todo este negocio de "no optimizar prematuramente" suena más como "Prefiere lento y no te preocupes porque las CPU son cada vez más rápidas y las aplicaciones CRUD no necesitan velocidad de todos modos". Sin embargo, puedo ser yo. :)
BobbyShaftoe
19
Lo que realmente significa es "El recolector de basura es mejor para administrar la memoria que usted" Aunque podría ser solo yo. :)
BobRodes
2
@BobbyShaftoe: Probablemente sea un error decir que "la optimización prematura es mala, siempre", como saltar al extremo opuesto de "suena más como 'preferir lento'". Ningún programador razonable diría tampoco. Se trata de matices y ser inteligente sobre lo que estás optimizando. Personalmente, me preocuparía la claridad del código y LUEGO PRUEBA REALMENTE el rendimiento, ya que personalmente he visto a mucha gente (incluido yo mismo cuando era más joven) dedicar demasiado tiempo a crear el algoritmo "perfecto", solo para que ahorre 0.1 ms en 100,000 iteraciones, todo mientras la legibilidad se disparó por completo.
Brent Rittenhouse
36

Otra razón para evitar establecer objetos en nulo cuando haya terminado con ellos es que en realidad puede mantenerlos vivos por más tiempo.

p.ej

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

permitirá que el objeto referido por someType sea GC'd después de la llamada a "DoSomething" pero

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

a veces puede mantener vivo el objeto hasta el final del método. El JIT generalmente optimiza la asignación a nulo , por lo que ambos bits de código terminan siendo los mismos.

Wilka
fuente
Ese es un punto interesante. Siempre pensé que los objetos no quedan fuera del alcance hasta que se completa el método en el que se definen. A menos que, por supuesto, el objeto esté dentro de un bloque Using o esté establecido explícitamente en Nothing o nulo.
Guru Josh
1
La forma preferida de garantizar que se mantengan con vida es usar GC.KeepAlive(someType); See ericlippert.com/2013/06/10/construction-destruction
NotMe
7

También:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of
Steve Tranby
fuente
7

En general, no es necesario anular objetos después del uso, pero en algunos casos creo que es una buena práctica.

Si un objeto implementa IDisposable y se almacena en un campo, creo que es bueno anularlo, solo para evitar usar el objeto desechado. Los errores del siguiente tipo pueden ser dolorosos:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

Es bueno anular el campo después de desecharlo y obtener un NullPtrEx justo en la línea donde el campo se usa nuevamente. De lo contrario, podría encontrarse con algún error críptico en el futuro (dependiendo exactamente de lo que haga DoSomething).

dbkk
fuente
8
Bueno, un objeto desechado debería arrojar ObjectDisposedException si ya ha sido desechado. Esto, por lo que sé, requiere un código repetitivo en todo el lugar, pero de nuevo, Disposed es un paradigma mal pensado de todos modos.
nicodemus13 05 de
3
Ctrl + F para .Dispose(). Si lo encuentra, no está utilizando IDisposable correctamente. El único uso para un objeto desechable debe estar dentro de los límites de un bloque de uso. Y después de usar el bloque, ya ni siquiera tiene acceso a él myField. Y dentro del bloque de uso, nullno es necesario establecerlo, el bloque de uso dispondrá el objeto por usted.
Suamere
7

Lo más probable es que su código no esté suficientemente estructurado si siente la necesidad de nullvariables.

Hay varias formas de limitar el alcance de una variable:

Como lo menciona Steve Tranby

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Del mismo modo, simplemente puede usar llaves:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Me parece que el uso de llaves sin ningún "encabezado" para realmente limpiar el código y ayudar a que sea más comprensible.

mbillard
fuente
Intenté usar ámbitos locales personalizados una vez (principalmente siendo un smarta $$). La empresa explotó.
Suamere
En otra nota: Esto se debe a que el compilador de c # encontrará variables de ámbito local que implementan IDisposable y llamará a .Dispose (la mayoría de las veces) cuando finalice su alcance. Sin embargo ... Las conexiones SQL son muy importantes cuando .Dispose () nunca está optimizado. Hay algunos tipos que requieren atención explícita, por lo que personalmente siempre hago las cosas explícitamente para no ser mordido.
Suamere
5

El único momento en que debe establecer una variable como nulo es cuando la variable no se sale del alcance y ya no necesita los datos asociados a ella. De lo contrario no hay necesidad.

Beto
fuente
2
Eso es cierto, pero también significa que probablemente debería refactorizar su código. No creo que alguna vez haya necesitado declarar una variable fuera de su alcance previsto.
Karl Seguin
2
Si se entiende que "variable" incluye campos de objeto, entonces esta respuesta tiene mucho sentido. En el caso donde "variable" significa solo "variable local" (de un método), entonces probablemente estamos hablando de casos de nicho aquí (por ejemplo, un método que se ejecuta durante un período de tiempo mucho más largo de lo habitual).
stakx - ya no contribuye
5

En general, no es necesario establecerlo en nulo. Pero suponga que tiene una funcionalidad Restablecer en su clase.

Entonces puede hacerlo, porque no desea llamar a dispose dos veces, ya que parte de Dispose puede no implementarse correctamente y generar la excepción System.ObjectDisposed.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }
Munish Goyal
fuente
Lo mejor es rastrear esto con una bandera separada, tal vez.
Thulani Chivandikwa
3

Este tipo de "no es necesario establecer objetos en nulo después del uso" no es del todo exacto. Hay veces que necesita NULL la variable después de desecharla.

Sí, SIEMPRE debe llamar .Dispose()o llamar .Close()a cualquier cosa que tenga cuando haya terminado. Ya se trate de identificadores de archivos, conexiones de bases de datos u objetos desechables.

Aparte de eso, está el patrón muy práctico de LazyLoad.

Decir que tengo y crea una instancia ObjAde class A. Class Atiene una propiedad pública llamada PropBde class B.

Internamente, PropBusa la variable privada de _By por defecto es nulo. Cuando PropB.Get()se utiliza, se comprueba si _PropBes nulo y si es así, abre los recursos necesarios para crear instancias de un Ba _PropB. Luego vuelve _PropB.

Según mi experiencia, este es un truco realmente útil.

Donde entra la necesidad de anular es si restablece o cambia A de alguna manera de la que el contenido _PropBera hijo de los valores anteriores A, deberá Eliminar Y _PropBanular para que LazyLoad pueda restablecer para obtener el valor correcto SI el código lo requiere

Si solo lo hace _PropB.Dispose()y poco después espera que la comprobación nula para LazyLoad tenga éxito, no será nula y verá datos obsoletos. En efecto, debe anularlo después Dispose()solo para estar seguro.

Seguramente desearía que fuera de otra manera, pero ahora tengo un código que muestra este comportamiento después de un Dispose()encendido _PropBy fuera de la función de llamada que hizo la Disposición (y, por lo tanto, casi fuera del alcance), el accesorio privado aún no es nulo, y los datos obsoletos siguen ahí.

Eventualmente, la propiedad dispuesta se anulará, pero eso no ha sido determinista desde mi perspectiva.

La razón principal, como alude dbkk, es que el contenedor padre ( ObjAcon PropB) mantiene la instancia _PropBdentro del alcance, a pesar de Dispose().

KenF
fuente
Un buen ejemplo que muestra cómo configurar nulo manualmente significa un error más fatal para la persona que llama, lo cual es algo bueno
llega el
1

Hay algunos casos en los que tiene sentido hacer referencias nulas. Por ejemplo, cuando está escribiendo una colección, como una cola prioritaria, y según su contrato, no debe mantener esos objetos vivos para el cliente después de que el cliente los haya eliminado de la cola.

Pero este tipo de cosas solo importa en colecciones de larga vida. Si la cola no va a sobrevivir al final de la función en la que se creó, entonces importa mucho menos.

En general, realmente no deberías molestarte. Deje que el compilador y GC hagan su trabajo para que pueda hacer el suyo.

Patricio
fuente
1

Eche un vistazo a este artículo también: http://www.codeproject.com/KB/cs/idisposable.aspx

En su mayor parte, establecer un objeto en nulo no tiene ningún efecto. El único momento en el que debe asegurarse de hacerlo es si está trabajando con un "objeto grande", que tiene un tamaño mayor a 84K (como mapas de bits).

Scott Dorman
fuente
1

Stephen Cleary explica muy bien en esta publicación: ¿Debería establecer variables en nulo para ayudar a la recolección de basura?

Dice:

La respuesta corta, para el impaciente Sí, si la variable es un campo estático, o si está escribiendo un método enumerable (utilizando el rendimiento de retorno) o un método asincrónico (utilizando asíncrono y espera). De otra manera no.

Esto significa que en los métodos regulares (no enumerables y no asíncronos), no establece variables locales, parámetros de método o campos de instancia como nulos.

(Incluso si está implementando IDisposable.Dispose, aún no debe establecer las variables en nulo).

Lo importante que debemos considerar son los campos estáticos .

Los campos estáticos siempre son objetos raíz , por lo que el recolector de basura siempre los considera "vivos" . Si un campo estático hace referencia a un objeto que ya no es necesario, debe establecerse en nulo para que el recolector de basura lo trate como elegible para la recolección.

Establecer campos estáticos en nulo no tiene sentido si todo el proceso se está cerrando. Todo el montón está a punto de recogerse en ese punto, incluidos todos los objetos raíz.

Conclusión:

Campos estáticos ; Eso es todo. Cualquier otra cosa es una pérdida de tiempo .

Ebleme
fuente
0

Creo que por el diseño de los implementadores de GC, no se puede acelerar GC con anulación. Estoy seguro de que preferiría que no se preocupe con la forma / GC cuando se ejecuta - tratarlo como esta omnipresente Ser proteger y velar y hacia fuera para usted ... (arcos cabeza hacia abajo, levanta el puño al cielo) .. .

Personalmente, a menudo establezco explícitamente las variables en nulas cuando termino con ellas como una forma de auto documentación. No declaro, uso, luego lo configuro como nulo más tarde: lo anulo inmediatamente después de que ya no son necesarios. Estoy diciendo, explícitamente: "Ya terminé oficialmente contigo ... vete ..."

¿La anulación es necesaria en un lenguaje GC'd? No. ¿Es útil para el GC? Tal vez sí, tal vez no, no lo sé con certeza, por diseño realmente no puedo controlarlo, e independientemente de la respuesta de hoy con esta o aquella versión, las futuras implementaciones de GC podrían cambiar la respuesta más allá de mi control. Además, si se optimiza la anulación, es poco más que un comentario elegante, por así decirlo .

Me imagino que si aclara mi intención al próximo tonto pobre que sigue mis pasos, y si "podría" ayudar a GC a veces, entonces vale la pena para mí. Principalmente me hace sentir ordenado y claro, y a Mongo le gusta sentirse ordenado y claro. :)

Lo veo así: los lenguajes de programación existen para permitir que las personas den a otras personas una idea de intención y un compilador solicite un trabajo sobre qué hacer: el compilador convierte esa solicitud en un idioma diferente (a veces varios) para una CPU. la (s) CPU (s) podría (n) desear qué idioma usó, su configuración de pestañas, comentarios, énfasis estilísticos, nombres de variables, etc. Muchas cosas escritas en el código no se convierten en lo que consume la CPU en la secuencia que especificamos. Nuestro C, C ++, C #, Lisp, Babel, ensamblador o lo que sea es teoría en lugar de realidad, escrito como una declaración de trabajo. Lo que ves no es lo que obtienes, sí, incluso en lenguaje ensamblador.

Entiendo que la mentalidad de "cosas innecesarias" (como líneas en blanco) "no son más que ruido y código desordenado". Ese era yo al principio de mi carrera; Lo entiendo totalmente. En este momento me inclino hacia lo que aclara el código. No es como si estuviera agregando incluso 50 líneas de "ruido" a mis programas, son algunas líneas aquí o allá.

Hay excepciones a cualquier regla. En escenarios con memoria volátil, memoria estática, condiciones de carrera, singletons, uso de datos "obsoletos" y todo ese tipo de podredumbre, eso es diferente: NECESITA administrar su propia memoria, bloqueando y anulando como corresponde porque la memoria no es parte de the GC'd Universe, espero que todos lo entiendan. El resto del tiempo con los lenguajes GC'd es una cuestión de estilo más que de necesidad o un aumento de rendimiento garantizado.

Al final del día, asegúrese de comprender qué es elegible para GC y qué no; bloquear, desechar y anular adecuadamente; encerar, encerar; inhala exhala; y por todo lo demás digo: si se siente bien, hazlo. Su kilometraje puede variar ... como debería ...

Juan
fuente
0

Creo que volver a poner algo en nulo es desordenado. Imagine un escenario en el que el elemento que se está configurando ahora está expuesto, por ejemplo, a través de la propiedad. Ahora, de alguna manera, algún fragmento de código usa accidentalmente esta propiedad después de que se elimina el elemento, obtendrá una excepción de referencia nula que requiere un poco de investigación para descubrir exactamente qué está sucediendo.

Creo que el marco desechable permitirá lanzar ObjectDisposedException, que es más significativo. No volver a establecerlos en nulo sería mejor que por ese motivo.

Thulani Chivandikwa
fuente
-1

Algunos objetos suponen el .dispose()método que obliga a eliminar el recurso de la memoria.

GateKiller
fuente
11
No, no lo hace; Dispose () no recolecta el objeto; se usa para realizar una limpieza determinista, generalmente liberando recursos no administrados.
Marc Gravell
1
Teniendo en cuenta que el determinismo se aplica sólo a los recursos gestionados, no los que no administrados (es decir, memoria)
nicodemus13