O objetivo de Dispose é liberar recursos não gerenciados. Isso precisa ser feito em algum momento, caso contrário eles nunca serão limpos. O coletor de lixo não sabe como chamar DeleteHandle()
uma variável do tipo IntPtr
, não sabe se precisa ou não chamar DeleteHandle()
.
Nota : O que é um recurso não gerenciado ? Se você o encontrou no Microsoft .NET Framework: ele é gerenciado. Se você foi bisbilhotar o MSDN, ele não é gerenciado. Qualquer coisa que você usou chamadas P / Invoke para sair do mundo agradável e agradável de tudo o que está disponível no .NET Framework não é gerenciada - e agora você é responsável por limpá-la.
O objeto que você criou precisa expor algum método, que o mundo externo pode chamar, para limpar recursos não gerenciados. O método pode ser nomeado como você quiser:
public void Cleanup()
ou
public void Shutdown()
Mas, em vez disso, existe um nome padronizado para este método:
public void Dispose()
Havia até uma interface criada IDisposable
, que possui apenas esse método:
public interface IDisposable
{
void Dispose()
}
Então, você faz seu objeto expor a IDisposable
interface e promete prometer que escreveu esse método único para limpar seus recursos não gerenciados:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
E você terminou. Exceto que você pode fazer melhor.
E se o seu objeto tiver alocado um System.Drawing.Bitmap de 250 MB (ou seja, a classe Bitmap gerenciada pelo .NET) como algum tipo de buffer de quadro? Claro, esse é um objeto .NET gerenciado e o coletor de lixo o libertará. Mas você realmente quer deixar 250MB de memória apenas sentado lá - esperando o coletor de lixo para , eventualmente, vir e libertá-la? E se houver uma conexão de banco de dados aberta ? Certamente, não queremos que a conexão fique aberta, esperando o GC finalizar o objeto.
Se o usuário chamou Dispose()
(o que significa que não planeja mais usar o objeto), por que não se livrar desses bitmaps e conexões de banco de dados desperdiçados?
Então agora vamos:
- livrar-se de recursos não gerenciados (porque precisamos) e
- livrar-se dos recursos gerenciados (porque queremos ser úteis)
Então, vamos atualizar nosso Dispose()
método para nos livrar desses objetos gerenciados:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
E tudo está bem, exceto que você pode fazer melhor !
E se a pessoa se esquecesse de chamar Dispose()
seu objeto? Então eles vazariam alguns recursos não gerenciados !
Nota: Eles não vazam recursos gerenciados , porque, eventualmente, o coletor de lixo será executado em um encadeamento em segundo plano e liberará a memória associada a qualquer objeto não utilizado. Isso incluirá seu objeto e todos os objetos gerenciados que você usa (por exemplo, oe Bitmap
o DbConnection
).
Se a pessoa esqueceu de ligar Dispose()
, ainda podemos salvar o bacon! Nós ainda temos uma maneira de chamá-lo para eles: quando o coletor de lixo, finalmente, fica em torno de libertar (ou seja, a finalização) nosso objeto.
Nota: O coletor de lixo eventualmente liberará todos os objetos gerenciados. Quando isso acontece, ele chama o Finalize
método no objeto. O GC não sabe nem se importa com o seu método Dispose . Esse foi apenas um nome que escolhemos para um método que chamamos quando queremos nos livrar de coisas não gerenciadas.
A destruição de nosso objeto pelo coletor de lixo é o momento perfeito para liberar esses recursos não gerenciados e irritantes. Fazemos isso substituindo o Finalize()
método
Nota: No C #, você não substitui explicitamente o Finalize()
método. Você escreve um método que se parece com um destruidor de C ++ , e o compilador considera essa a sua implementação do Finalize()
método:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Mas há um erro nesse código. Veja bem, o coletor de lixo é executado em um encadeamento em segundo plano ; você não sabe a ordem em que dois objetos são destruídos. É perfeitamente possível que, no seu Dispose()
código, o objeto gerenciado do qual você está tentando se livrar (porque queria ser útil) não esteja mais lá:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Portanto, o que você precisa é uma maneira de Finalize()
dizer Dispose()
que ele não deve tocar em nenhum recurso gerenciado (porque eles podem não estar lá) mais ), enquanto ainda libera recursos não gerenciados.
O padrão padrão para fazer isso é ter Finalize()
e Dispose()
chamar um terceiro método (!); de onde você passa um ditado booleano, se você está ligando para Dispose()
(em oposição a Finalize()
), o que significa que é seguro liberar recursos gerenciados.
Esse método interno pode receber um nome arbitrário como "CoreDispose" ou "MyInternalDispose", mas é tradição chamá-lo Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Mas um nome de parâmetro mais útil pode ser:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
E você altera sua implementação do IDisposable.Dispose()
método para:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
e seu finalizador para:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Nota : Se o seu objeto descende de um objeto implementado Dispose
, não se esqueça de chamar o método Dispose de base quando você substituir o Dispose:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
E tudo está bem, exceto que você pode fazer melhor !
Se o usuário chamar Dispose()
seu objeto, tudo será limpo. Mais tarde, quando o coletor de lixo aparecer e chamar Finalizar, ele chamará Dispose
novamente.
Não é apenas um desperdício, mas se o seu objeto tiver referências indesejadas para os objetos que você já descartou desde a última chamada Dispose()
, você tentará descartá-los novamente!
Você notará no meu código que tomei o cuidado de remover referências a objetos que eu descartei, para não tentar chamar Dispose
uma referência a objetos indesejados. Mas isso não impediu que um bug sutil aparecesse.
Quando o usuário chama Dispose()
: o identificador CursorFileBitmapIconServiceHandle é destruído. Mais tarde, quando o coletor de lixo for executado, ele tentará destruir o mesmo identificador novamente.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
A maneira como você corrige isso é informar ao coletor de lixo que ele não precisa se preocupar em finalizar o objeto - seus recursos já foram limpos e não é necessário mais trabalho. Você faz isso chamando GC.SuppressFinalize()
o Dispose()
método:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Agora que o usuário ligou Dispose()
, temos:
- liberou recursos não gerenciados
- recursos gerenciados liberados
Não faz sentido o GC executar o finalizador - tudo foi resolvido.
Não foi possível usar o Finalize para limpar recursos não gerenciados?
A documentação para Object.Finalize
diz:
O método Finalize é usado para executar operações de limpeza em recursos não gerenciados mantidos pelo objeto atual antes que o objeto seja destruído.
Mas a documentação do MSDN também diz, para IDisposable.Dispose
:
Executa tarefas definidas pelo aplicativo associadas à liberação, liberação ou redefinição de recursos não gerenciados.
Então qual é? Qual é o lugar para eu limpar recursos não gerenciados? A resposta é:
É a sua escolha! Mas escolha Dispose
.
Você certamente pode colocar sua limpeza não gerenciada no finalizador:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
O problema é que você não tem idéia de quando o coletor de lixo estará pronto para finalizar seu objeto. Seus recursos nativos não gerenciados, desnecessários e não utilizados permanecerão até o coletor de lixo eventualmente ser executado. Em seguida, ele chamará o seu método finalizador; limpeza de recursos não gerenciados. A documentação do Object.Finalize aponta isso:
A hora exata em que o finalizador é executado é indefinida. Para garantir a liberação determinística de recursos para instâncias de sua classe, implemente um método Close ou forneça uma IDisposable.Dispose
implementação.
Essa é a virtude de usar Dispose
para limpar recursos não gerenciados; você conhece e controla quando os recursos não gerenciados são limpos. Sua destruição é "determinística" .
Para responder à sua pergunta original: Por que não liberar memória agora, e não para quando o GC decide fazer isso? Eu tenho um software de reconhecimento facial que precisa se livrar de 530 MB de imagens internas agora , já que elas não são mais necessárias. Quando não o fazemos: a máquina fica parada.
Leitura de bônus
Para quem gosta do estilo desta resposta (explicando o porquê , de modo que o como se torna óbvio), sugiro que você leia o capítulo um do COM essencial de Don Box:
Em 35 páginas, ele explica os problemas do uso de objetos binários e inventa o COM diante de seus olhos. Depois de perceber o porquê do COM, as 300 páginas restantes são óbvias e apenas detalham a implementação da Microsoft.
Eu acho que todo programador que já lidou com objetos ou COM deve, no mínimo, ler o primeiro capítulo. É a melhor explicação de tudo que existe.
Leitura de bônus extra
Quando tudo o que você sabe está errado por Eric Lippert
Portanto, é realmente muito difícil escrever um finalizador correto, e o melhor conselho que posso dar é não tentar .