O .NET IDisposable Pattern implica que, se você escrever um finalizador e implementar IDisposable, seu finalizador precisará chamar explicitamente Dispose. Isso é lógico e é o que eu sempre fiz nas raras situações em que um finalizador é garantido.
No entanto, o que acontece se eu apenas fizer isso:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
e não implemente um finalizador, ou qualquer coisa. A estrutura chamará o método Dispose para mim?
Sim, percebo que isso parece idiota, e toda a lógica implica que não, mas sempre tive duas coisas na parte de trás da minha cabeça que me deixaram insegura.
Alguém, alguns anos atrás, me disse uma vez que, de fato, faria isso, e essa pessoa tinha um histórico muito sólido de "conhecer suas coisas".
O compilador / estrutura faz outras coisas 'mágicas', dependendo de quais interfaces você implementa (por exemplo: foreach, métodos de extensão, serialização baseada em atributos, etc.), por isso faz sentido que também possa ser 'mágico'.
Embora tenha lido muitas coisas sobre o assunto e muitas coisas estejam implícitas, nunca consegui encontrar uma resposta definitiva Sim ou Não a esta pergunta.
fonte
Quero enfatizar o argumento de Brian em seu comentário, porque é importante.
Os finalizadores não são destruidores determinísticos, como no C ++. Como outros já apontaram, não há garantia de quando será chamado e, de fato, se você tiver memória suficiente, se algum dia será chamado.
Mas o ruim dos finalizadores é que, como Brian disse, isso faz com que seu objeto sobreviva a uma coleta de lixo. Isso pode ser ruim. Por quê?
Como você pode ou não saber, o GC é dividido em gerações - Gen 0, 1 e 2, mais o Large Object Heap. Dividir é um termo flexível - você obtém um bloco de memória, mas há indicadores de onde os objetos Gen 0 começam e terminam.
O processo de pensamento é que você provavelmente usará muitos objetos que terão vida curta. Portanto, esses devem ser fáceis e rápidos para o GC chegar aos objetos - Gen 0. Portanto, quando há pressão de memória, a primeira coisa que faz é uma coleção Gen 0.
Agora, se isso não resolver a pressão suficiente, ele voltará e fará uma varredura da Geração 1 (refazendo a Geração 0) e, se ainda não for suficiente, fará uma varredura da Geração 2 (refazendo a Geração 1 e Geração 0). Portanto, a limpeza de objetos de longa duração pode demorar um pouco e ser bastante cara (já que seus threads podem ser suspensos durante a operação).
Isso significa que se você fizer algo assim:
Seu objeto, não importa o quê, viverá para a Geração 2. Isso ocorre porque o GC não tem como chamar o finalizador durante a coleta de lixo. Portanto, os objetos que precisam ser finalizados são movidos para uma fila especial a ser limpa por um thread diferente (o thread do finalizador - que, se você mata, faz com que todos os tipos de coisas ruins aconteçam). Isso significa que seus objetos ficam mais tempo e potencialmente forçam mais coletas de lixo.
Portanto, tudo isso é apenas para esclarecer o ponto em que você deseja usar o IDisposable para limpar os recursos sempre que possível e tentar seriamente encontrar maneiras de usar o finalizador. É no melhor interesse do seu aplicativo.
fonte
Já há muita discussão boa aqui, e estou um pouco atrasado para a festa, mas queria acrescentar alguns pontos.
Essa é a versão simples, mas há muitas nuances que podem te enganar nesse padrão.
Na minha opinião, é muito melhor evitar completamente qualquer tipo que contenha diretamente referências descartáveis e recursos nativos que possam exigir finalização. O SafeHandles fornece uma maneira muito clara de fazer isso, encapsulando recursos nativos em descartáveis que fornecem internamente sua própria finalização (junto com vários outros benefícios, como remover a janela durante P / Invoke, em que um identificador nativo pode ser perdido devido a uma exceção assíncrona) .
Simplesmente definir um SafeHandle torna isso trivial:
Permite simplificar o tipo que contém para:
fonte
GC.SuppressFinalize
neste exemplo. Nesse contexto, SuppressFinalize só deve ser chamado se forDispose(true)
executado com êxito. Se houverDispose(true)
falha em algum momento após a finalização ser suprimida, mas antes de todos os recursos (principalmente os não gerenciados) serem limpos, você ainda desejará que a finalização ocorra para fazer o máximo de limpeza possível. Melhor mover aGC.SuppressFinalize
chamada para oDispose()
método após a chamada paraDispose(true)
. Consulte as Diretrizes de design da estrutura e este post .Acho que não. Você tem controle sobre quando Dispose é chamado, o que significa que, em teoria, você poderia escrever um código de descarte que faz suposições sobre (por exemplo) a existência de outros objetos. Você não tem controle sobre quando o finalizador é chamado, portanto, seria duvidoso que o finalizador chamasse Dispose automaticamente em seu nome.
EDIT: Eu fui e testei, apenas para ter certeza:
fonte
Não no caso descrito, mas o GC chamará o Finalizador para você, se você tiver um.
CONTUDO. A próxima coleta de lixo, em vez de ser coletada, o objeto entra na fila de finalização, tudo é coletado e, em seguida, é finalizado. A próxima coleção depois disso será liberada.
Dependendo da pressão de memória do seu aplicativo, você pode não ter um gc para a geração desse objeto por um tempo. Portanto, no caso, digamos, um fluxo de arquivos ou uma conexão db, talvez seja necessário aguardar um pouco para que o recurso não gerenciado seja liberado na chamada do finalizador por um tempo, causando alguns problemas.
fonte
Não, não é chamado.
Mas isso facilita a não esquecer de descartar seus objetos. Basta usar a
using
palavra - chave.Eu fiz o seguinte teste para isso:
fonte
O GC não chamará de descarte. Ele pode chamar seu finalizador, mas mesmo isso não é garantido em todas as circunstâncias.
Consulte este artigo para uma discussão sobre a melhor maneira de lidar com isso.
fonte
A documentação em IDisposable fornece uma explicação bastante clara e detalhada do comportamento, bem como um código de exemplo. O GC NÃO chamará o
Dispose()
método na interface, mas chamará o finalizador para o seu objeto.fonte
O padrão IDisposable foi criado principalmente para ser chamado pelo desenvolvedor, se você tiver um objeto que implementa o IDispose, o desenvolvedor deve implementar a
using
palavra-chave em torno do contexto do objeto ou chamar o método Dispose diretamente.O fail safe para o padrão é implementar o finalizador chamando o método Dispose (). Se você não fizer isso, poderá criar vazamentos de memória, por exemplo: Se você criar algum wrapper COM e nunca chamar o System.Runtime.Interop.Marshall.ReleaseComObject (comObject) (que seria colocado no método Dispose).
Não há mágica no clr para chamar métodos Dispose automaticamente, além de rastrear objetos que contêm finalizadores e armazená-los na tabela Finalizador pelo GC e chamá-los quando algumas heurísticas de limpeza são iniciadas pelo GC.
fonte