Estableciendo un objeto nulo frente a Dispose()


93

Estoy fascinado por la forma en que funcionan CLR y GC (estoy trabajando para ampliar mi conocimiento al leer CLR a través de C#, los libros/publicaciones de Jon Skeet, y más).

De todos modos, ¿cuál es la diferencia entre decir:

MyClass myclass = new MyClass(); 
myclass = null; 

O, al hacer MiClase implementar IDisposable y un destructor y llamar a Dispose()?

Además, si tengo un bloque de código con una instrucción using (p. Ej. A continuación), si paso por el código y salgo del bloque using, ¿se elimina el objeto entonces o cuando se produce una recolección de basura? ¿Qué pasaría si llamo a Dispose() en el bloque de uso?

using (MyDisposableObj mydispobj = new MyDisposableObj()) 
{ 

} 

Las clases de secuencias (por ejemplo, BinaryWriter) tienen un método Finalize? ¿Por qué querría usar eso?

185

Es importante separar la eliminación de la recolección de basura. Son cosas completamente separadas, con un punto en común al cual llegaré en un minuto.

Dispose, recolección de basura y la finalización

Cuando se escribe una declaración using, que es el azúcar simple sintáctica para un bloque finally/oportunidad para que Dispose se llama incluso si el código en el cuerpo de la declaración using lanza una excepción. Es no significa que el objeto es basura recogida al final del bloque.

La eliminación es acerca de recursos no administrados (recursos sin memoria). Estos podrían ser identificadores UI, conexiones de red, manejadores de archivos, etc. Estos son recursos limitados, por lo que generalmente desea liberarlos tan pronto como sea posible. Debe implementar IDisposable siempre que su tipo "posee" un recurso no administrado, ya sea directamente (generalmente a través de IntPtr) o indirectamente (por ejemplo, a través de Stream, SqlConnection, etc.).

La recolección de elementos basureros solo tiene que ver con la memoria, con un pequeño giro. El recolector de basura puede encontrar objetos a los que ya no se puede hacer referencia, y liberarlos. Sin embargo, no busca basura todo el tiempo, solo cuando detecta que necesita (por ejemplo, si una "generación" del montón se queda sin memoria).

El giro es finalización. El recolector de basura mantiene una lista de objetos que ya no son alcanzables, pero que tienen un finalizador (escrito como ~Foo() en C#, algo confuso, no se parecen en nada a los destructores de C++). Ejecuta los finalizadores en estos objetos, en caso de que necesiten hacer una limpieza adicional antes de liberar su memoria.

Los finalizadores se usan casi siempre para limpiar recursos en el caso en que el usuario del tipo se haya olvidado de deshacerse de él de manera ordenada. Por lo tanto, si abre un FileStream pero se olvida de llamar al Dispose o Close, el finalizador dará con el tiempo liberará el manejador del archivo subyacente por usted. En un programa bien escrito, los finalizadores casi nunca deberían dispararse en mi opinión.

Configuración de una variable a null

Un pequeño punto sobre la configuración de una variable a null - esto casi nunca es necesario por el bien de la recolección de basura. A veces, puede querer hacerlo si se trata de una variable miembro, aunque en mi experiencia es raro que la "parte" de un objeto ya no sea necesaria. Cuando se trata de una variable local, el JIT suele ser lo suficientemente inteligente (en el modo de lanzamiento) para saber cuándo no va a utilizar una referencia nuevamente. Por ejemplo:

StringBuilder sb = new StringBuilder(); 
sb.Append("Foo"); 
string x = sb.ToString(); 

// The string and StringBuilder are already eligible 
// for garbage collection here! 
int y = 10; 
DoSomething(y); 

// These aren't helping at all! 
x = null; 
sb = null; 

// Assume that x and sb aren't used here 

La única vez en que puede valer la pena establecer una variable local para null es cuando estás en un bucle, y algunas ramas del bucle necesita usar la variable pero se sabe Llegué a un punto en el que no lo hizo. Por ejemplo:

SomeObject foo = new SomeObject(); 

for (int i=0; i < 100000; i++) 
{ 
    if (i == 5) 
    { 
     foo.DoSomething(); 
     // We're not going to need it again, but the JIT 
     // wouldn't spot that 
     foo = null; 
    } 
    else 
    { 
     // Some other code 
    } 
} 

de aplicación IDisposable/finalizadores

Por lo tanto, deben poner en práctica sus propios tipos de finalizadores? Casi seguro que no. Si solo indirectamente retienes recursos no administrados (p.tiene una FileStream como variable miembro) y luego agregar su propio finalizador no será de ayuda: la transmisión casi con certeza será elegible para la recolección de basura cuando su objeto es, por lo que puede confiar en que FileStream tenga un finalizador (si es necesario - puede referirse a otra cosa, etc.). Si desea mantener "casi" directamente un recurso no administrado, SafeHandle es su amigo, toma un poco de tiempo, pero significa almostnever need to write a finalizer again. Por lo general, solo necesita un finalizador si tiene un control directo sobre un recurso (un IntPtr) y debería pasar al SafeHandle tan pronto como sea posible. (Hay dos enlaces allí; lea ambos, idealmente).

Joe Duffy tiene very long set of guidelines around finalizers and IDisposable (coescrito con mucha gente inteligente) que vale la pena leer. Vale la pena tener en cuenta que si cierras tus clases, la vida es mucho más fácil: el patrón de anular Dispose para llamar a un nuevo método virtual Dispose(bool) etc. solo es relevante cuando tu clase está diseñada para herencia.

Este ha sido un poco de un paseo, pero por favor, pedir una aclaración donde desea alguna :)

  0

Re "El único momento en el que valdría la pena establecer una variable local en nulo" - quizás también algunos de los escenarios más espinosos de "captura" (capturas múltiples de la misma variable) - ¡pero puede que no valga la pena complicar la publicación! +1 ... 22 feb. 092009-02-22 10:10:13

  0

@Marc: Es cierto, ni siquiera había pensado en las variables capturadas. Hmm. Sí, creo que lo dejaré solo;) 22 feb. 092009-02-22 10:19:50

+9

Wow. Finalmente entiendo la fuente del culto de los skeetistas. ¡Esta publicación es increíble! 24 feb. 092009-02-24 17:20:35

  0

¿podría decirnos qué sucederá cuando configure "foo = null" en el fragmento de código anterior? Por lo que sé, ¿esa línea solo borra el valor de una variable que apunta al objeto foo en el montón administrado? así que la pregunta es ¿qué pasará con el objeto foo allí? ¿No deberíamos llamar a disponer de eso? 25 abr. 102010-04-25 04:41:02

  0

@odiseh: Si el objeto es desechable, entonces sí, debe desecharlo. Esa sección de la respuesta solo se refería a la recolección de basura, que está completamente separada. 25 abr. 102010-04-25 08:01:50

  0

+1 en particular, por: * "Vale la pena tener en cuenta que si sellas tus clases, la vida es mucho más fácil: el patrón de anulación Dispose para invocar un nuevo método virtual Dispose (bool), etc. solo es relevante cuando tu clase está diseñado para la herencia. "* 04 mar. 142014-03-04 17:31:53

+1

Estaba buscando una aclaración sobre algunas inquietudes identificables, así que busqué en Google" IDSposable Skeet "y encontré esto. ¡Estupendo! : D 06 ene. 152015-01-06 15:34:47

  0

@odiseh, después de establecer en nulo, no puede llamar a disponer, ya que lvalue se vuelve nulo. sin embargo, si ese objeto tenía referencias a la memoria que se redirigió, esas referencias podrían agregarse a la generación de memoria que el recolector de basura limpiará en breve. 23 jun. 172017-06-23 06:00:14


19

Cuando desecha un objeto, los recursos se liberan. Cuando asigna null a una variable, solo está cambiando una referencia.

myclass = null; 

Después de ejecutar esto, el objeto miclase se refería a todavía existe, y continuará hasta que el GC consigue alrededor de la limpieza. Si se llama explícitamente a Dispose, o está en un bloque de uso, todos los recursos se liberarán tan pronto como sea posible.

+7

Se puede * * Todavía no existir después de ejecutar esa línea - que puede haber sido recogida de basura * antes * esa linea. El JIT es inteligente, lo que hace que las líneas como esta sean casi irrelevantes. 22 feb. 092009-02-22 09:49:13

+6

La configuración en null podría significar que los recursos mantenidos por el objeto * nunca * se liberan. El GC no dispone, solo finaliza, por lo que si el objeto contiene directamente recursos no administrados y su finalizador no dispone (o no tiene un finalizador), entonces esos recursos tendrán fugas. Algo a tener en cuenta. 22 feb. 092009-02-22 12:11:25


4

Las dos operaciones no tienen mucho que ver entre sí. Cuando establece una referencia a null, simplemente lo hace. En sí mismo no afecta la clase a la que se hizo referencia en absoluto. Su variable simplemente ya no apunta al objeto que solía, pero el objeto en sí no ha cambiado.

Cuando llama a Dispose(), es una llamada de método al objeto en sí. Independientemente del método Dispose, ahora se hace en el objeto. Pero esto no afecta su referencia al objeto.

La única área de superposición es que cuando No hay más referencias a un objeto, que se finalmente consiguen basura recogida. Y si la clase implementa la interfaz IDisposable, se llamará a Dispose() sobre el objeto antes de que se recolecte la basura.

Pero eso no sucederá inmediatamente después de establecer su referencia a nulo, por dos razones. En primer lugar, pueden existir otras referencias, por lo que aún no se recogerá basura, y en segundo lugar, incluso si esa fue la última referencia, por lo que ahora está lista para ser recogida de basura, no pasará nada hasta que el recolector de basura decida eliminar el objeto.

Llamar a Dispose() sobre un objeto no "mata" al objeto de ninguna manera. Comúnmente se usa para limpiar para que el objeto pueda ser borrado después de forma segura, pero en última instancia, no hay nada de mágico en Dispose, es solo un método de clase.

  0

Creo que esta respuesta complementa o es un detalle de la respuesta de "recursivo". 22 feb. 092009-02-22 01:08:12