É importante separar o descarte da coleta de lixo. São coisas completamente distintas, com um ponto em comum que abordarei em um minuto.
Dispose
, coleta de lixo e finalização
Quando você escreve uma using
instrução, é simplesmente um açúcar sintático para um bloco try / finally, de modo que Dispose
é chamado mesmo se o código no corpo da using
instrução lançar uma exceção. Isso não significa que o objeto seja coletado como lixo no final do bloco.
O descarte é sobre recursos não gerenciados ( recursos que não são de memória). Podem ser identificadores de IU, conexões de rede, identificadores de arquivo, etc. Esses são recursos limitados, então geralmente você deseja liberá-los assim que puder. Você deve implementar IDisposable
sempre que seu tipo "possuir" um recurso não gerenciado, seja diretamente (geralmente por meio de um IntPtr
) ou indiretamente (por exemplo Stream
, por meio de um , a SqlConnection
etc.).
A coleta de lixo em si é apenas sobre memória - com um pequeno toque. O coletor de lixo é capaz de localizar objetos que não podem mais ser referenciados e liberá-los. Porém, ele não procura por lixo o tempo todo - apenas quando detecta que precisa (por exemplo, se uma "geração" do heap ficar sem memória).
A reviravolta é a finalização . O coletor de lixo mantém uma lista de objetos que não são mais alcançáveis, mas que têm um finalizador (escrito como ~Foo()
em C #, um tanto confuso - eles não são nada como destruidores C ++). Ele executa os finalizadores nesses objetos, apenas no caso de eles precisarem fazer uma limpeza extra antes que sua memória seja liberada.
Os finalizadores quase sempre são usados para limpar recursos no caso em que o usuário do tipo se esqueceu de descartá-los de maneira ordenada. Portanto, se você abrir um FileStream
mas esquecer de chamar Dispose
ou Close
, o finalizador eventualmente liberará o identificador de arquivo subjacente para você. Em um programa bem escrito, os finalizadores quase nunca deveriam disparar na minha opinião.
Definir uma variável para null
Um pequeno ponto sobre como definir uma variável para null
- isso quase nunca é necessário para a coleta de lixo. Às vezes, você pode querer fazer isso se for uma variável de membro, embora em minha experiência seja raro que "parte" de um objeto não seja mais necessária. Quando é uma variável local, o JIT geralmente é inteligente o suficiente (no modo de liberação) para saber quando você não usará uma referência novamente. Por exemplo:
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
A única vez em que pode valer a pena definir uma variável local null
é quando você está em um loop, e alguns ramos do loop precisam usar a variável, mas você sabe que atingiu um ponto em que não o faz. Por exemplo:
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 IDisponíveis / finalizadores
Portanto, seus próprios tipos devem implementar finalizadores? Quase certamente não. Se você apenas indiretamente mantém recursos não gerenciados (por exemplo, você tem um FileStream
como uma variável de membro), então adicionar seu próprio finalizador não ajudará: o fluxo quase certamente será elegível para coleta de lixo quando seu objeto for, então você pode apenas confiar em FileStream
ter um finalizador (se necessário - pode se referir a outra coisa, etc). Se você deseja manter um recurso não gerenciado "quase" diretamente, SafeHandle
é seu amigo - leva um pouco de tempo para começar, mas significa que você quase nunca precisará escrever um finalizador novamente . Você normalmente só precisa de um finalizador se tiver um controle realmente direto sobre um recurso (an IntPtr
) e deve procurar mover paraSafeHandle
assim que puder. (Existem dois links lá - leia ambos, de preferência.)
Joe Duffy tem um longo conjunto de diretrizes sobre finalizadores e IDisposable (co-escrito com muitas pessoas inteligentes) que vale a pena ler. Vale a pena estar ciente de que se você selar suas classes, torna a vida muito mais fácil: o padrão de sobrescrever Dispose
para chamar um novo Dispose(bool)
método virtual etc. só é relevante quando sua classe é projetada para herança.
Isso tem sido um pouco confuso, mas por favor, peça esclarecimentos onde você gostaria de alguns :)