Establecer un objeto nulo frente a Dispose ()

Estoy fascinado por la forma en que funcionan el 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 que MyClass implemente IDisposable y un destructor y llame a Dispose ()?

Además, si tengo un bloque de código con una instrucción de uso (por ejemplo, a continuación), si paso por el código y salgo del bloque de uso, ¿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 flujo (por ejemplo, BinaryWriter) tienen un método Finalize? ¿Por qué querría usar eso?

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 finalización

Cuando se escribe una instrucción using , se trata simplemente de azúcar sintáctico para un bloque try / finally, por lo que se llama Dispose incluso si el código en el cuerpo de la sentencia using arroja una excepción. No significa que el objeto sea basura recolectada al final del bloque.

La eliminación se trata de recursos no administrados ( recursos que no son de 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 cada vez que su tipo “posee” un recurso no administrado, ya sea directamente (generalmente a través de un IntPtr ) o indirectamente (por ejemplo, a través de un Stream , un SqlConnection etc.).

La recolección de basura 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 es necesario (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 accesibles, pero que tienen un finalizador (escrito como ~Foo() en C #, algo confuso, no se parecen en nada a los destructores C ++). Ejecuta los finalizadores en estos objetos, en caso de que necesiten hacer una limpieza adicional antes de liberar su memoria.

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

Establecer una variable a null

Un pequeño punto al establecer una variable como 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 que puede valer la pena establecer una variable local en null es cuando está en un bucle, y algunas twigs del bucle necesitan usar la variable, pero sabe que ha llegado a un punto en el que no lo hace. 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 } } 

Implementando IDisposable / finalizers

Entonces, ¿sus propios tipos deberían implementar finalizadores? Casi seguro que no. Si solo tienes indirectamente recursos no administrados (por ejemplo, tienes un FileStream como variable miembro), agregar tu propio finalizador no ayudará: la transmisión casi con seguridad será elegible para la recolección de basura cuando tu objeto esté, así que puedes confiar en FileStream con un finalizador (si es necesario, puede referirse a otra cosa, etc.). Si desea mantener "casi" directamente un recurso no administrado, SafeHandle es su amigo; le lleva un poco de tiempo, pero casi nunca tendrá que volver a escribir un finalizador . Por lo general, solo debería necesitar un finalizador si tiene un control directo sobre un recurso (un IntPtr ) y debería pasar a SafeHandle tan pronto como sea posible. (Hay dos enlaces allí; lee ambos, idealmente).

Joe Duffy tiene un conjunto muy extenso de pautas sobre los finalizadores y IDisposable (co-escrito 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 anulación Dispose para invocar un nuevo método virtual Dispose(bool) , etc. solo es relevante cuando tu clase está diseñada para la herencia.

Esto ha sido un poco divagar, pero solicite una aclaración sobre dónde le gustaría 🙂

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 al que se refería myclass todavía existe, y continuará hasta que el GC lo solucione. 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.

Las dos operaciones no tienen mucho que ver con las demá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 mismo no se modifica.

Cuando llamas 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 solapamiento es que cuando no hay más referencias a un objeto, eventualmente obtendrá basura recolectada. 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. Primero, pueden existir otras referencias, por lo que aún no se recogerá basura, y segundo, 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 eliminarla el objeto.

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