Es importante separar la eliminación de la recolección de basura. Son cosas completamente separadas, con un punto en común al que llegaré en un minuto.
Dispose
, recolección y finalización de basura
Cuando escribe una using
declaración, es simplemente azúcar sintáctico para un bloque try / finalmente, por lo que Dispose
se llama incluso si el código en el cuerpo de la using
declaración arroja una excepción. Es no significa que el objeto es basura recogida al final del bloque.
La eliminación se refiere a recursos no administrados ( recursos que no son de memoria). Estos pueden ser identificadores de interfaz de usuario, conexiones de red, identificadores de archivos, etc. Son recursos limitados, por lo que generalmente querrá liberarlos tan pronto como sea posible. Debe implementar IDisposable
siempre que su tipo "posea" un recurso no administrado, ya sea directamente (generalmente a través de un IntPtr
) o indirectamente (por ejemplo, a través de a Stream
, a, SqlConnection
etc.).
La recolección de basura en sí solo se trata de 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 lo necesita (por ejemplo, si una "generación" del montón se queda sin memoria).
El giro es la 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 son nada como destructores de C ++). Ejecuta los finalizadores en estos objetos, por si acaso necesitan hacer una limpieza adicional antes de que se libere su memoria.
Los finalizadores casi siempre se utilizan para limpiar recursos en el caso de que el usuario del tipo haya olvidado deshacerse de ellos de manera ordenada. Entonces, si abre un FileStream
pero se olvida de llamar Dispose
o Close
, el finalizador eventualmente liberará el identificador del archivo subyacente por usted. En un programa bien escrito, los finalizadores casi nunca deberían dispararse, en mi opinión.
Establecer una variable en null
Un pequeño punto al establecer una variable en null
: esto casi nunca es necesario por el bien de la recolección de basura. Es posible que a veces desee hacerlo si es una variable miembro, aunque en mi experiencia es raro que "parte" de un objeto ya no sea necesaria. Cuando se trata de una variable local, el JIT suele ser lo suficientemente inteligente (en modo de lanzamiento) como para saber cuándo no volverá a usar una referencia. 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
El único momento en el que puede valer la pena establecer una variable local null
es cuando estás en un bucle, y algunas ramas del bucle necesitan usar la variable, pero sabes que has llegado a un punto en el que no. 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 / finalizadores
Entonces, ¿sus propios tipos deberían implementar finalizadores? Es casi seguro que no. Si solo tiene recursos no administrados indirectamente (por ejemplo, tiene una FileStream
variable miembro), agregar su propio finalizador no ayudará: es casi seguro que la transmisión sea elegible para la recolección de basura cuando su objeto lo sea, por lo que puede confiar en FileStream
tener un finalizador (si es necesario, puede referirse a otra cosa, etc.). Si desea mantener un recurso no administrado "casi" directamente, ¿ SafeHandle
es su amigo? Se necesita un poco de tiempo para empezar, pero significa que casi nunca necesitará escribir un finalizador de nuevo . Por lo general, solo debería necesitar un finalizador si tiene un control realmente directo sobre un recurso (an IntPtr
) y debería buscar pasar aSafeHandle
tan pronto como puedas. (Hay dos enlaces allí, lea ambos, idealmente).
Joe Duffy tiene un conjunto muy extenso de pautas sobre finalizadores e IDisposable (coescrito con mucha gente inteligente) que vale la pena leer. Vale la pena tener en cuenta que si sellas tus clases, la vida será mucho más fácil: el patrón de anulación Dispose
para llamar a un nuevo Dispose(bool)
método virtual , etc., solo es relevante cuando tu clase está diseñada para herencia.
Esto ha sido un poco complicado, pero pide una aclaración sobre dónde te gustaría :)