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 :)
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 ... –
@Marc: Es cierto, ni siquiera había pensado en las variables capturadas. Hmm. Sí, creo que lo dejaré solo;) –
Wow. Finalmente entiendo la fuente del culto de los skeetistas. ¡Esta publicación es increíble! – JohnFx