Sei da leitura da documentação da Microsoft que o uso "primário" da IDisposable
interface é limpar recursos não gerenciados.
Para mim, "não gerenciado" significa coisas como conexões com bancos de dados, soquetes, identificadores de janelas etc. Mas vi código em que o Dispose()
método é implementado para liberar recursos gerenciados , o que me parece redundante, pois o coletor de lixo deve cuidar de isso para você.
Por exemplo:
public class MyCollection : IDisposable
{
private List<String> _theList = new List<String>();
private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();
// Die, clear it up! (free unmanaged resources)
public void Dispose()
{
_theList.clear();
_theDict.clear();
_theList = null;
_theDict = null;
}
Minha pergunta é: isso faz com que o coletor de lixo libere a memória usada MyCollection
mais rapidamente do que normalmente?
editar : Até agora, as pessoas publicaram alguns bons exemplos de uso do IDisposable para limpar recursos não gerenciados, como conexões com bancos de dados e bitmaps. Mas suponha que _theList
no código acima contivesse um milhão de strings e você desejasse liberar essa memória agora , em vez de esperar pelo coletor de lixo. O código acima conseguiria isso?
fonte
IDisposable
não marca nada. ODispose
método faz o que é necessário para limpar os recursos usados pela instância. Isso não tem nada a ver com o GC.IDisposable
. E foi por isso que disse que a resposta aceita não responde à pergunta pretendida pelo OP (e edição posterior) sobre se o IDisposable ajudará na <i> liberação de memória </i>. ComoIDisposable
não tem nada a ver com liberar memória, apenas recursos, então, como você disse, não há necessidade de definir as referências gerenciadas como nulas, o que o OP estava fazendo no seu exemplo. Portanto, a resposta correta para sua pergunta é "Não, não ajuda a liberar memória mais rapidamente. De fato, não ajuda a liberar memória, apenas recursos". Mas de qualquer forma, obrigado pela sua contribuição.Respostas:
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 tipoIntPtr
, não sabe se precisa ou não chamarDeleteHandle()
.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:
ou
Mas, em vez disso, existe um nome padronizado para este método:
Havia até uma interface criada
IDisposable
, que possui apenas esse método: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: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:
Então, vamos atualizar nosso
Dispose()
método para nos livrar desses objetos gerenciados: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 !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.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étodoMas 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á:Portanto, o que você precisa é uma maneira de
Finalize()
dizerDispose()
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()
eDispose()
chamar um terceiro método (!); de onde você passa um ditado booleano, se você está ligando paraDispose()
(em oposição aFinalize()
), 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)
:Mas um nome de parâmetro mais útil pode ser:
E você altera sua implementação do
IDisposable.Dispose()
método para:e seu finalizador para:
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.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()
oDispose()
método:Agora que o usuário ligou
Dispose()
, temos: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:Mas a documentação do MSDN também diz, para
IDisposable.Dispose
:Então qual é? Qual é o lugar para eu limpar recursos não gerenciados? A resposta é:
Você certamente pode colocar sua limpeza não gerenciada no finalizador:
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:
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
fonte
null
. Primeiro de tudo, isso significa que você não pode fazê-losreadonly
e, em segundo lugar, é necessário fazer!=null
verificações muito feias (como no código de exemplo). Você pode ter uma bandeiradisposed
, mas é mais fácil não se preocupar com isso. O GC do .NET é agressivo o suficiente para que uma referência a um campox
não seja mais contada como 'usada' no momento em que passa ax.Dispose()
linha.IDisposable
é frequentemente usado para explorar ausing
instrução e tirar proveito de uma maneira fácil de fazer a limpeza determinística dos objetos gerenciados.fonte
O objetivo do padrão Dispose é fornecer um mecanismo para limpar recursos gerenciados e não gerenciados e, quando isso ocorrer, depende de como o método Dispose está sendo chamado. No seu exemplo, o uso de Dispose não está realmente fazendo nada relacionado ao descarte, pois a limpeza de uma lista não afeta a coleta que está sendo descartada. Da mesma forma, as chamadas para definir as variáveis como nulas também não afetam o GC.
Você pode dar uma olhada neste artigo para obter mais detalhes sobre como implementar o padrão Dispose, mas basicamente se parece com isso:
O método mais importante aqui é o Dispose (bool), que na verdade é executado sob duas circunstâncias diferentes:
O problema de simplesmente deixar o GC cuidar da limpeza é que você não tem controle real sobre quando o GC executará um ciclo de coleta (você pode chamar GC.Collect (), mas não deveria) para que os recursos permaneçam por mais tempo do que o necessário. Lembre-se, chamar Dispose () na verdade não causa um ciclo de coleta ou de qualquer forma faz com que o GC colete / libere o objeto; simplesmente fornece os meios para uma limpeza mais determinística dos recursos utilizados e informa ao GC que essa limpeza já foi realizada.
O ponto principal de IDisposable e o padrão de descarte não é liberar imediatamente a memória. A única vez em que uma chamada para Dispose realmente tem a chance de liberar memória imediatamente é quando está lidando com o cenário == false de eliminação e manipulando recursos não gerenciados. Para código gerenciado, a memória não será recuperada até que o GC execute um ciclo de coleta, sobre o qual você realmente não tem controle (exceto chamar GC.Collect (), que eu já mencionei, não é uma boa idéia).
Seu cenário não é realmente válido, pois as strings no .NET não usam recursos não gerenciados e não implementam IDisposable, não há como forçá-las a serem "limpas".
fonte
Não deve haver mais chamadas para os métodos de um objeto depois que Dispose foi chamado (embora um objeto deva tolerar mais chamadas para Dispose). Portanto, o exemplo na pergunta é bobo. Se Dispose for chamado, o próprio objeto poderá ser descartado. Portanto, o usuário deve apenas descartar todas as referências a esse objeto inteiro (defini-las como nulas) e todos os objetos relacionados internos a ele serão limpos automaticamente.
Quanto à pergunta geral sobre gerenciado / não gerenciado e a discussão em outras respostas, acho que qualquer resposta a essa pergunta deve começar com uma definição de recurso não gerenciado.
O que se resume é que existe uma função que você pode chamar para colocar o sistema em um estado, e há outra função que você pode chamar para trazê-lo de volta desse estado. Agora, no exemplo típico, o primeiro pode ser uma função que retorna um identificador de arquivo e o segundo pode ser uma chamada para
CloseHandle
.Mas - e esta é a chave - eles podem ser qualquer par de funções correspondente. Um constrói um estado, o outro destrói. Se o estado foi construído, mas ainda não foi demolido, existe uma instância do recurso. É necessário organizar a desmontagem no momento certo - o recurso não é gerenciado pelo CLR. O único tipo de recurso gerenciado automaticamente é a memória. Existem dois tipos: o GC e a pilha. Os tipos de valor são gerenciados pela pilha (ou pegando uma carona dentro dos tipos de referência) e os tipos de referência são gerenciados pelo GC.
Essas funções podem causar alterações de estado que podem ser intercaladas livremente ou podem precisar ser perfeitamente aninhadas. As alterações de estado podem ser seguras, ou podem não.
Veja o exemplo na pergunta de Justice. Alterações na indentação do arquivo de log devem estar perfeitamente aninhadas ou tudo dá errado. Também é improvável que eles sejam seguros para threads.
É possível pegar uma carona com o coletor de lixo para limpar seus recursos não gerenciados. Mas somente se as funções de alteração de estado forem seguras contra thread e dois estados puderem ter vidas úteis que se sobrepõem de alguma maneira. Portanto, o exemplo de recurso de Justice NÃO deve ter um finalizador! Isso simplesmente não ajudaria ninguém.
Para esses tipos de recursos, você pode simplesmente implementar
IDisposable
, sem um finalizador. O finalizador é absolutamente opcional - tem que ser. Isso é encoberto ou nem mencionado em muitos livros.Você precisa usar a
using
instrução para ter a chance de garantir que elaDispose
seja chamada. Isso é essencialmente como pegar uma carona com a pilha (assim como o finalizador é para o GC,using
é para a pilha).A parte que falta é que você precise escrever Dispose manualmente e fazer com que ele chame seus campos e sua classe base. Os programadores de C ++ / CLI não precisam fazer isso. O compilador escreve para eles na maioria dos casos.
Existe uma alternativa, que eu prefiro para estados que se aninham perfeitamente e não são seguros para threads (além de qualquer outra coisa, evitar o IDisposable poupa o problema de ter uma discussão com alguém que não resiste a adicionar um finalizador a todas as classes que implementam o IDisposable) .
Em vez de escrever uma classe, você escreve uma função. A função aceita um delegado para retornar a chamada:
E então um exemplo simples seria:
O lambda sendo passado serve como um bloco de código, então é como se você tivesse sua própria estrutura de controle para servir ao mesmo propósito que
using
, exceto que você não tem mais o risco de o chamador abusar dele. Não há como eles falharem na limpeza do recurso.Essa técnica é menos útil se o recurso é do tipo que pode ter vida útil sobreposta, porque você deseja criar o recurso A, depois o recurso B, depois matar o recurso A e depois matar o recurso B. Você não pode fazer isso se você forçou o usuário a aninhar-se perfeitamente assim. Mas então você precisa usar
IDisposable
(mas ainda sem um finalizador, a menos que você tenha implementado a segurança de thread, o que não é gratuito).fonte
enter
eexit
é o núcleo de como penso em um recurso. A inscrição / cancelamento de inscrição em eventos deve ser feita sem dificuldade. Em termos das características ortogonais / fungíveis, é praticamente indistinguível de um vazamento de memória. (Isto não é surpreendente como assinatura é apenas adicionando objetos a uma lista.)Cenários que utilizo IDisposable: limpar recursos não gerenciados, cancelar a inscrição para eventos, fechar conexões
O idioma que eu uso para implementar IDisposable ( não thread-safe ):
fonte
Sim, esse código é completamente redundante e desnecessário e não faz com que o coletor de lixo faça o que não faria (caso uma instância do MyCollection fique fora do escopo, isso é).
.Clear()
chamadas.Responda à sua edição: Mais ou menos. Se eu fizer isso:
É funcionalmente idêntico a isso para fins de gerenciamento de memória:
Se você realmente precisar liberar a memória neste instante, ligue
GC.Collect()
. Não há razão para fazer isso aqui, no entanto. A memória será liberada quando necessário.fonte
Se
MyCollection
for coletar lixo de qualquer maneira, não será necessário descartá-lo. Fazer isso apenas agitará a CPU mais do que o necessário e pode até invalidar algumas análises pré-calculadas que o coletor de lixo já executou.Eu costumo
IDisposable
fazer coisas como garantir que os threads sejam descartados corretamente, juntamente com recursos não gerenciados.EDIT Em resposta ao comentário de Scott:
Conceitualmente, o GC mantém uma visão do gráfico de referência do objeto e todas as referências a ele a partir dos quadros de pilha de encadeamentos. Esse heap pode ser bastante grande e abranger muitas páginas de memória. Como uma otimização, o GC armazena em cache suas análises de páginas que provavelmente não são alteradas com frequência, para evitar digitalizar novamente a página desnecessariamente. O GC recebe uma notificação do kernel quando os dados em uma página são alterados, para que ele saiba que a página está suja e exige uma nova verificação. Se a coleção estiver em Gen0, é provável que outras coisas na página também estejam mudando, mas isso é menos provável em Gen1 e Gen2. Curiosamente, esses ganchos não estavam disponíveis no Mac OS X para a equipe que transportou o GC para o Mac, a fim de fazer o plug-in do Silverlight trabalhar nessa plataforma.
Outro ponto contra o descarte desnecessário de recursos: imagine uma situação em que um processo está sendo descarregado. Imagine também que o processo está em execução há algum tempo. Provavelmente, muitas das páginas de memória desse processo foram trocadas para o disco. No mínimo, eles não estão mais no cache L1 ou L2. Em tal situação, não há sentido em um aplicativo que está descarregando trocar todos os dados e páginas de código de volta na memória para 'liberar' recursos que serão liberados pelo sistema operacional de qualquer maneira quando o processo terminar. Isso se aplica a recursos gerenciados e até a certos recursos não gerenciados. Somente os recursos que mantêm os segmentos não em segundo plano ativos devem ser descartados, caso contrário, o processo permanecerá ativo.
Agora, durante a execução normal, existem recursos efêmeros que devem ser limpos corretamente (como @fezmonkey aponta conexões de banco de dados, soquetes, identificadores de janela ) para evitar vazamentos de memória não gerenciados. Esses são os tipos de coisas que precisam ser descartadas. Se você criar alguma classe que possua um encadeamento (e por possuir, quero dizer que ele o criou e, portanto, é responsável por garantir que ele pare, pelo menos pelo meu estilo de codificação), essa classe provavelmente deverá implementar
IDisposable
e desmontar o encadeamento duranteDispose
.A estrutura .NET usa a
IDisposable
interface como um sinal, mesmo aviso, aos desenvolvedores de que essa classe deve ser descartada. Não consigo pensar em nenhum tipo de estrutura que implementeIDisposable
(excluindo implementações explícitas de interface) em que o descarte é opcional.fonte
Dispose()
chamadas opcionais , consulte: stackoverflow.com/questions/913228/…No exemplo que você postou, ele ainda não "libera a memória agora". Toda a memória é coletada como lixo, mas pode permitir que a memória seja coletada em uma geração anterior . Você precisaria executar alguns testes para ter certeza.
As diretrizes de design da estrutura são diretrizes e não regras. Eles informam para que serve a interface, quando usá-la, como usá-la e quando não usá-la.
Certa vez, li um código que era um RollBack () simples quando falha usando IDisposable. A classe MiniTx abaixo marcaria um sinalizador em Dispose () e, se a
Commit
chamada nunca acontecesse, chamariaRollback
a si mesma. Ele adicionou uma camada de indireção, tornando o código de chamada muito mais fácil de entender e manter. O resultado foi algo como:Também vi código de temporização / registro fazer a mesma coisa. Nesse caso, o método Dispose () parou o cronômetro e registrou que o bloco havia saído.
Então, aqui estão alguns exemplos concretos que não fazem nenhuma limpeza não gerenciada de recursos, mas usam com êxito o IDisposable para criar um código mais limpo.
fonte
Se você deseja excluir agora , use memória não gerenciada .
Vejo:
fonte
Não repetirei as coisas usuais sobre o uso ou a liberação de recursos não gerenciados, que já foram cobertos. Mas gostaria de salientar o que parece ser um equívoco comum.
Dado o seguinte código
Sei que a implementação descartável não segue as diretrizes atuais, mas espero que todos vocês entendam a idéia.
Agora, quando Dispose é chamado, quanta memória é liberada?
Resposta: Nenhuma.
Chamar Dispose pode liberar recursos não gerenciados, NÃO PODE recuperar a memória gerenciada, apenas o GC pode fazer isso. Isso não quer dizer que a descrição acima não seja uma boa ideia, seguir o padrão acima ainda é uma boa ideia. Depois que o Dispose é executado, nada impede o GC de reivindicar novamente a memória que estava sendo usada pelo _Large, mesmo que a instância do LargeStuff ainda esteja no escopo. As strings em _Large também podem estar na geração 0, mas a instância do LargeStuff pode ser a geração 2; portanto, novamente, a memória seria reivindicada mais cedo.
Não faz sentido adicionar um finalizador para chamar o método Dispose mostrado acima. Isso atrasará apenas a reivindicação de memória para permitir que o finalizador seja executado.
fonte
LargeStuff
já existe há tempo suficiente para chegar à Geração 2 e se_Large
mantém uma referência a uma sequência recém-criada que está na Geração 0, se a instância deLargeStuff
for abandonada sem anular_Large
, a sequência referida por_Large
será mantido até a próxima coleção Gen2. O zerar_Large
pode deixar a string ser eliminada na próxima coleção Gen0. Na maioria dos casos, anular referências não é útil, mas há casos em que ela pode oferecer algum benefício.Além de seu uso principal como uma maneira de controlar a vida útil dos recursos do sistema (completamente coberto pela incrível resposta de Ian , parabéns!), O combo IDisposable / using também pode ser usado para definir a mudança de estado dos recursos globais (críticos) : o console , os threads , o processo , qualquer objeto global como uma instância de aplicativo .
Eu escrevi um artigo sobre esse padrão: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
Ele ilustra como você pode proteger alguns estados globais usados de maneira reutilizável e legível : cores do console , cultura atual do encadeamento , propriedades do objeto de aplicativo do Excel ...
fonte
Se alguma coisa, eu esperaria que o código fosse menos eficiente do que quando o deixasse de fora.
Chamar os métodos Clear () é desnecessário, e o GC provavelmente não faria isso se o Dispose não o fizesse ...
fonte
Há coisas que a
Dispose()
operação faz no código de exemplo que podem ter um efeito que não ocorreria devido a um GC normal doMyCollection
objeto.Se os objetos referenciados por
_theList
ou_theDict
forem referidos por outros objetos, esseList<>
ouDictionary<>
objeto não estará sujeito à coleção, mas de repente não terá conteúdo. Se não houvesse operação Dispose () como no exemplo, essas coleções ainda conteriam seu conteúdo.É claro que, se essa fosse a situação, eu chamaria isso de design quebrado - apenas estou apontando (supostamente) que a
Dispose()
operação pode não ser completamente redundante, dependendo se há outros usosList<>
ouDictionary<>
não. mostrado no fragmento.fonte
Um problema com a maioria das discussões sobre "recursos não gerenciados" é que eles realmente não definem o termo, mas parecem sugerir que ele tem algo a ver com código não gerenciado. Embora seja verdade que muitos tipos de recursos não gerenciados fazem interface com código não gerenciado, pensar em recursos não gerenciados nesses termos não é útil.
Em vez disso, deve-se reconhecer o que todos os recursos gerenciados têm em comum: todos eles envolvem um objeto que solicita que alguma coisa externa faça algo em seu nome, em detrimento de outras 'coisas', e a outra entidade concorda em fazê-lo até aviso prévio. Se o objeto fosse abandonado e desaparecesse sem deixar vestígios, nada diria àquela "coisa" externa que ele não precisava mais alterar seu comportamento em nome do objeto que não existia mais; consequentemente, a utilidade da coisa seria permanentemente diminuída.
Um recurso não gerenciado, portanto, representa um acordo de alguma coisa externa para alterar seu comportamento em nome de um objeto, o que seria inútil prejudicar a utilidade dessa "coisa" externa se o objeto fosse abandonado e deixasse de existir. Um recurso gerenciado é um objeto que é o beneficiário desse contrato, mas que se inscreveu para receber uma notificação se for abandonado e que usará essa notificação para colocar seus negócios em ordem antes de ser destruído.
fonte
IDisposable
é bom para cancelar a inscrição de eventos.fonte
Primeiro da definição. Para mim, recurso não gerenciado significa alguma classe, que implementa a interface IDisposable ou algo criado com o uso de chamadas para dll. O GC não sabe como lidar com esses objetos. Se, por exemplo, classe tem apenas tipos de valor, não considero essa classe como classe com recursos não gerenciados. Para o meu código, sigo as próximas práticas:
seguir gera o que eu descrevi em palavras como amostra de código:
fonte
is IDisposable
deveria ser considerado um recurso não gerenciado? Isso não parece correto. Além disso, se o tipo de implementação for um tipo de valor puro, você parece sugerir que ele não precisa ser descartado. Isso também parece errado.Seu exemplo de código fornecido não é um bom exemplo de
IDisposable
uso. A limpeza de dicionário normalmente não deve ir para oDispose
método Os itens do dicionário serão limpos e descartados quando ficarem fora do escopo.IDisposable
A implementação é necessária para liberar algumas memórias / manipuladores que não serão liberados / liberados mesmo depois que estiverem fora do escopo.O exemplo a seguir mostra um bom exemplo para o padrão IDisposable com alguns códigos e comentários.
fonte
O caso de uso mais justificável para o descarte de recursos gerenciados é a preparação para o GC recuperar recursos que, de outra forma, nunca seriam coletados.
Um exemplo principal são as referências circulares.
Embora seja uma prática recomendada usar padrões que evitem referências circulares, se você acabar com (por exemplo) um objeto 'filho' que tenha uma referência ao 'pai', isso poderá interromper a coleta de GC do pai, se você abandonar a referência e confie no GC - além disso, se você implementou um finalizador, ele nunca será chamado.
A única maneira de contornar isso é quebrar manualmente as referências circulares, definindo as referências Pai como nulas nos filhos.
Implementar IDisposable em pais e filhos é a melhor maneira de fazer isso. Quando Dispose é chamado no Pai, chame Dispose em todos os Filhos e, no método Dispose filho, defina as referências do Pai como nulas.
fonte
WeakReference
, o sistema verifica uma sinalização que indica que uma referência raiz ao vivo foi encontrada no último ciclo da GC , e adicionará o objeto a uma fila de objetos que precisam de finalização imediata, liberará o objeto do heap de objeto grande ou invalidará a referência fraca. Os árbitros circulares não manterão objetos vivos se não houver outros árbitros.Vejo que muitas respostas mudaram para falar sobre o uso de IDisposable para recursos gerenciados e não gerenciados. Eu sugeriria este artigo como uma das melhores explicações que eu descobri sobre como o IDisposable deve realmente ser usado.
https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About
Para a pergunta real; Se você usar o IDisposable para limpar objetos gerenciados que ocupam muita memória, a resposta curta seria não . O motivo é que, depois de descartar um IDisposable, você deve deixá-lo sair do escopo. Nesse ponto, todos os objetos filhos referenciados também estão fora do escopo e serão coletados.
A única exceção real a isso seria se você tiver muita memória amarrada em objetos gerenciados e tiver bloqueado esse segmento aguardando a conclusão de alguma operação. Se os objetos onde não serão necessários após a conclusão da chamada, definir essas referências como nulas poderá permitir que o coletor de lixo as colete mais rapidamente. Mas esse cenário representaria um código incorreto que precisava ser refatorado - não um caso de uso de IDisposable.
fonte