Uso adequado da interface IDisposable

Eu sei de ler a documentação do MSDN que o uso “primário” da interface IDisposable é limpar resources não gerenciados.

Para mim, “não gerenciado” significa coisas como conexões de database, sockets, identificadores de janela, etc. Mas vi o código onde o método Dispose() é implementado para liberar resources gerenciados , o que parece redundante para mim, já que o coletor de lixo deveria Cuide disso para você.

Por exemplo:

 public class MyCollection : IDisposable { private List _theList = new List(); private Dictionary _theDict = new Dictionary(); // Die, clear it up! (free unmanaged resources) public void Dispose() { _theList.clear(); _theDict.clear(); _theList = null; _theDict = null; } 

Minha pergunta é: isso faz a memory livre do coletor de lixo usada pelo MyCollection mais rápida do que normalmente?

edit : Até agora, as pessoas postaram alguns bons exemplos do uso de IDisposable para limpar resources não gerenciados, como conexões de database e bitmaps. Mas suponha que _theList no código acima continha um milhão de strings, e você queria liberar essa memory agora , em vez de esperar pelo coletor de lixo. O código acima conseguiria isso?

O ponto de Dispose é liberar resources 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() em uma variável do tipo IntPtr , ele não sabe se precisa ou não chamar DeleteHandle() .

Nota : O que é um recurso não gerenciado ? Se você encontrou no Microsoft .NET Framework: ele é gerenciado. Se você andou usando o MSDN por conta própria, ele não foi gerenciado. Qualquer coisa que você tenha usado chamadas P / Invoke para sair do agradável e confortável mundo de tudo que está disponível para você no .NET Framwork não é gerenciado – e agora você é responsável por limpá-lo.

O object que você criou precisa expor algum método, que o mundo externo pode chamar, para limpar resources não gerenciados. O método pode ser nomeado como você quiser:

 public void Cleanup() public void Shutdown() 

Mas, em vez disso, há um nome padronizado para esse método:

 public void Dispose() 

Houve até uma interface criada, IDisposable , que tem apenas um método:

 public interface IDisposable { void Dispose() } 

Assim, você faz com que seu object exponha a interface IDisposable e, dessa forma, você promete que escreveu esse método único para limpar seus resources não gerenciados:

 public void Dispose() { Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); } 

E pronto. Exceto que você pode fazer melhor.


E se o seu object tiver alocado um System.Drawing.Bitmap de 250MB (ou seja, a class Bitmap gerenciada pelo .NET) como algum tipo de buffer de frameworks? Claro, este é um object .NET gerenciado, e o coletor de lixo irá liberá-lo. Mas você realmente quer deixar 250MB de memory apenas sentado lá – esperando que o coletor de lixo eventualmente venha e liberte-o? E se houver uma conexão de database aberta ? Certamente não queremos que essa conexão fique aberta, esperando que o GC finalize o object.

Se o usuário tiver chamado Dispose() (significando que eles não planejam mais usar o object), por que não se livrar desses bitmaps e conexões de database desnecessários?

Então agora vamos:

  • livrar-se de resources não gerenciados (porque temos que) e
  • livrar-se de resources gerenciados (porque queremos ser úteis)

Então vamos atualizar nosso método Dispose() para se livrar desses objects 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 é bom, exceto que você pode fazer melhor !


E se a pessoa esqueceu de chamar Dispose() em seu object? Então eles vazariam alguns resources não gerenciados !

Nota: Eles não perderão resources gerenciados , porque, eventualmente, o coletor de lixo será executado em um encadeamento de plano de fundo e liberará a memory associada a qualquer object não utilizado. Isso includeá seu object e quaisquer objects gerenciados usados ​​(por exemplo, o Bitmap e o DbConnection ).

Se a pessoa esqueceu de ligar para Dispose() , ainda podemos salvar seu bacon! Ainda temos uma maneira de chamá-lo para eles: quando o coletor de lixo finalmente consegue liberar (ou seja, finalizar) nosso object.

Nota: O coletor de lixo acabará liberando todos os objects gerenciados. Quando isso acontecer, ele chama o método Finalize no object. O GC não sabe, ou se importa, sobre 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 do nosso object pelo coletor de lixo é o momento perfeito para liberar esses resources não gerenciados traquinas. Fazemos isso substituindo o método Finalize() .

Nota: Em C #, você não substitui explicitamente o método Finalize() . Você escreve um método que se parece com um destruidor de C ++ , e o compilador considera que essa é sua implementação do método Finalize() :

 ~MyObject() { //we're being finalized (ie destroyed), call Dispose in case the user forgot to Dispose(); //<--Warning: subtle bug! Keep reading! } 

Mas há um bug nesse código. Você vê, o coletor de lixo é executado em um segmento de plano de fundo ; você não sabe a ordem em que dois objects são destruídos. É inteiramente possível que no seu código Dispose() , o object gerenciado que você está tentando se livrar (porque você 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; } } 

Então, o que você precisa é uma maneira de Finalize() dizer a Dispose() que ele não deve tocar em nenhum recurso gerenciado (porque eles podem não estar mais ), enquanto ainda libera resources não gerenciados.

O padrão padrão para fazer isso é fazer com que Finalize() e Dispose() chame um terceiro método (!); onde você passa um booleano dizendo se você está chamando de Dispose() (ao contrário de Finalize() ), o que significa que é seguro liberar resources gerenciados.

Esse método interno poderia receber algum 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 método IDisposable.Dispose() 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 seu object desce de um object que implementa Dispose , então não se esqueça de chamar seu método Dispose base quando você replace Dispose:

 public Dispose() { try { Dispose(true); //true: safe to free managed resources } finally { base.Dispose(); } } 

E tudo é bom, exceto que você pode fazer melhor !


Se o usuário chamar Dispose() no seu object, tudo foi limpo. Mais tarde, quando o coletor de lixo aparecer e chamar Finalize, ele chamará Dispose novamente.

Isso não só é um desperdício, mas se o seu object tiver referências indesejadas a objects que você já descartou da última chamada para Dispose() , você tentará descartá-los novamente!

Você notará no meu código que eu tive o cuidado de remover referências a objects que eu descartei, então eu não tente chamar Dispose em uma referência de object de lixo. Mas isso não impediu que um inseto sutil se insinuasse.

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 de corrigir isso é dizer ao coletor de lixo que ele não precisa se preocupar em finalizar o object - seus resources já foram limpos e não é necessário mais nenhum trabalho. Você faz isso chamando GC.SuppressFinalize() no método Dispose() :

 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 chamou Dispose() , temos:

  • liberou resources não gerenciados
  • resources gerenciados liberados

Não há nenhum ponto no GC executando o finalizador - tudo está resolvido.

Não consegui usar o Finalize para limpar resources não gerenciados?

A documentação para Object.Finalize diz:

O método Finalize é usado para executar operações de limpeza em resources não gerenciados mantidos pelo object atual antes que o object 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 resources não gerenciados.

Então, qual é? Qual é o lugar para eu limpar resources não gerenciados? A resposta é:

É a sua escolha! Mas escolha Dispose .

Você certamente poderia colocar sua limpeza não gerenciada no finalizador:

 ~MyObject() { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //AC# destructor automatically calls the destructor of its base class. } 

O problema é que você não tem idéia de quando o coletor de lixo irá finalizar o seu object. Seus resources nativos não gerenciados, desnecessários e não usados ​​permanecerão até que o coletor de lixo seja executado. Em seguida, ele chamará seu método finalizador; limpar resources 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 resources para instâncias de sua class, implemente um método Close ou forneça uma implementação IDisposable.Dispose .

Essa é a virtude de usar o Dispose para limpar resources não gerenciados; você conhece e controla quando os resources não gerenciados são limpos. Sua destruição é "determinista" .


Para responder à sua pergunta original: por que não liberar memory agora, em vez de quando a 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 treme para uma parada de troca.

Leitura de bônus

Para quem gosta do estilo desta resposta (explicando o porquê , então o como se torna óbvio), sugiro que você leia o Capítulo Um do COM Essencial de Don Box:

  • Link direto: Chapter 1 sample by Pearson Publishing
  • ímã: 84bf0b960936d677190a2be355858e80ef7542c0

Em 35 páginas ele explica os problemas de usar objects binários e inventa 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 objects ou COM deve, no mínimo, ler o primeiro capítulo. É a melhor explicação de qualquer coisa.

Leitura Extra de Bônus

Quando tudo que você sabe está errado por Eric Lippert

Portanto, é muito difícil escrever um finalizador correto, e o melhor conselho que posso dar é não tentar .

IDisposable é frequentemente usado para explorar a instrução using e aproveitar uma maneira fácil de fazer limpeza determinística de objects gerenciados.

 public class LoggingContext : IDisposable { public Finicky(string name) { Log.Write("Entering Log Context {0}", name); Log.Indent(); } public void Dispose() { Log.Outdent(); } public static void Main() { Log.Write("Some initial stuff."); try { using(new LoggingContext()) { Log.Write("Some stuff inside the context."); throw new Exception(); } } catch { Log.Write("Man, that was a heavy exception caught from inside a child logging context!"); } finally { Log.Write("Some final stuff."); } } } 

O objective do padrão Dispose é fornecer um mecanismo para limpar os resources gerenciados e não gerenciados e, quando isso ocorrer, depende de como o método Dispose está sendo chamado. Em seu exemplo, o uso de Dispose não está realmente fazendo nada relacionado a descartar, já que limpar uma lista não tem impacto sobre a coleta que está sendo descartada. Da mesma forma, as chamadas para definir as variables ​​como nulas também não têm impacto no 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:

 public class SimpleCleanup : IDisposable { // some fields that require cleanup private SafeHandle handle; private bool disposed = false; // to detect redundant calls public SimpleCleanup() { this.handle = /*...*/; } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Dispose managed resources. if (handle != null) { handle.Dispose(); } } // Dispose unmanaged managed resources. disposed = true; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } 

O método mais importante aqui é o Dispose (bool), que é executado em duas circunstâncias diferentes:

  • disposing == true: o método foi chamado direta ou indiretamente pelo código de um usuário. Recursos gerenciados e não gerenciados podem ser descartados.
  • disposing == false: o método foi chamado pelo tempo de execução de dentro do finalizador e você não deve referenciar outros objects. Somente resources não gerenciados podem ser descartados.

O problema de simplesmente permitir que o GC cuide de fazer a 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 resources permaneçam por mais tempo do que o necessário. Lembre-se de que chamar Dispose () na verdade não causa um ciclo de coleta ou, de alguma forma, faz com que o GC colete / liberte o object; ele simplesmente fornece os meios para limpar de forma mais determinística os resources utilizados e informar ao GC que essa limpeza já foi executada.

O ponto inteiro de IDisposable e o padrão de descarte não é liberar imediatamente a memory. A única vez que uma chamada para Dispose realmente terá uma chance de liberar memory imediatamente é quando ela estiver manipulando o cenário disposing == false e manipulando resources não gerenciados. Para código gerenciado, a memory não será realmente recuperada até que o GC execute um ciclo de coleta, sobre o qual você realmente não tem controle (além de 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 resources não-gerenciados e não implementam IDisposable, não há como forçá-los a serem “limpos”.

Não deve haver mais chamadas para os methods de um object após Dispose ter sido chamado (embora um object deva tolerar outras chamadas para Dispose). Portanto, o exemplo na pergunta é bobo. Se Dispose for chamado, o próprio object poderá ser descartado. Portanto, o usuário deve descartar todas as referências a esse object inteiro (defini-las como nulas) e todos os objects relacionados internos a ele serão automaticamente limpos.

Quanto à questão 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 um recurso não gerenciado.

O que se resume é que existe uma function que você pode chamar para colocar o sistema em um estado, e há outra function que você pode chamar para trazê-lo de volta desse estado. Agora, no exemplo típico, o primeiro pode ser uma function 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. Um constrói um estado, o outro o derruba. Se o estado foi construído, mas ainda não foi demolido, existe uma instância do recurso. Você precisa providenciar para que a desassembly aconteça no momento certo – o recurso não é gerenciado pelo CLR. O único tipo de recurso gerenciado automaticamente é a memory. Existem dois tipos: o GC e a pilha. Os tipos de valor são gerenciados pela pilha (ou por 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 livremente intercaladas ou podem precisar ser perfeitamente aninhadas. As mudanças de estado podem ser thread-safe, ou podem não ser.

Veja o exemplo da pergunta de Justice. Alterações no recuo do arquivo de log devem estar perfeitamente aninhadas ou tudo dá errado. Também é improvável que eles sejam seguros.

É possível pegar uma carona com o coletor de lixo para limpar seus resources não gerenciados. Mas somente se as funções de mudança de estado forem thread-safe e dois estados puderem ter vidas úteis que se sobreponham de alguma forma. Por exemplo, o exemplo de Justiça de um recurso NÃO deve ter um finalizador! Isso não ajudaria ninguém.

Para esses tipos de resources, você pode simplesmente implementar IDisposable , sem um finalizador. O finalizador é absolutamente opcional – tem que ser. Isso é encoberto ou nem sequer mencionado em muitos livros.

Você então tem que usar a instrução using para ter alguma chance de assegurar que Dispose seja chamado. Isto é 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ê tem que escrever manualmente Dispose e fazê-lo chamar em seus campos e sua class base. Programadores 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 além de qualquer outra coisa, evitar IDisposable poupa-lhe o problema de ter um argumento com alguém que não resista a adicionar um finalizador a todas as classs que implementam IDisposable) .

Em vez de escrever uma aula, você escreve uma function. A function aceita um delegado para ligar de volta para:

 public static void Indented(this Log log, Action action) { log.Indent(); try { action(); } finally { log.Outdent(); } } 

E então um exemplo simples seria:

 Log.Write("Message at the top"); Log.Indented(() => { Log.Write("And this is indented"); Log.Indented(() => { Log.Write("This is even more indented"); }); }); Log.Write("Back at the outermost level again"); 

O lambda sendo passado serve como um bloco de código, então é como se você fizesse sua própria estrutura de controle para servir ao mesmo propósito do using , exceto que você não tem mais nenhum perigo de o chamador abusar dela. Não há como eles não conseguirem limpar o recurso.

Essa técnica é menos útil se o recurso for do tipo que pode ter tempos de vida sobrepostos, porque então você poderá construir 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 tenha implementado o threadsafety, que não é gratuito).

Cenários Eu faço uso de IDisposable: limpar resources não gerenciados, cancelar a inscrição para events, fechar conexões

O idioma que uso para implementar IDisposable ( não threadsafe ):

 class MyClass : IDisposable { // ... #region IDisposable Members and Helpers private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { // cleanup code goes here } disposed = true; } } ~MyClass() { Dispose(false); } #endregion } 

Se MyCollection for coletado como lixo, você não precisará descartá-lo. Fazê-lo apenas agitará a CPU mais do que o necessário e poderá até invalidar algumas análises pré-calculadas que o coletor de lixo já executou.

Eu uso IDisposable para fazer coisas como garantir threads são descartados corretamente, juntamente com resources não gerenciados.

EDIT Em resposta ao comentário de Scott:

O único momento em que as métricas de desempenho do GC são afetadas é quando uma chamada é feita pelo [GC] GC.collect () “

Conceitualmente, o GC mantém uma visão do gráfico de referência do object e todas as referências a ele dos frameworks de pilha dos encadeamentos. Esse heap pode ser muito grande e abranger muitas páginas de memory. Como uma otimização, o GC armazena em cache sua análise de páginas que provavelmente não serão alteradas com frequência para evitar a nova varredura da página desnecessariamente. O GC recebe uma notificação do kernel quando os dados em uma página são alterados, portanto, ele sabe que a página está suja e requer 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 portou o GC para Mac a fim de obter o plug-in do Silverlight trabalhando nessa plataforma.

Outro ponto contra o descarte desnecessário de resources: imagine uma situação em que um processo está sendo descarregado. Imagine também que o processo está em execução há algum tempo. As chances são de que muitas das páginas de memory do 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á nenhum ponto para um aplicativo que está descarregando para trocar todos esses dados e páginas de códigos de volta na memory para ‘liberar’ resources que serão liberados pelo sistema operacional de qualquer maneira quando o processo terminar. Isso se aplica a resources gerenciados e até mesmo certos não gerenciados. Somente os resources que mantêm os segmentos que não são de segundo plano devem ser descartados, caso contrário, o processo permanecerá ativo.

Agora, durante a execução normal, há resources efêmeros que devem ser limpos corretamente (como @fezmonkey aponta conexões de database, sockets, identificadores de janela ) para evitar vazamentos de memory não gerenciados. Esses são os tipos de coisas que precisam ser descartadas. If you create some class that owns a thread (and by owns I mean that it created it and therefore is responsible for ensuring it stops, at least by my coding style), then that class most likely must implement IDisposable and tear down the thread during Dispose .

The .NET framework uses the IDisposable interface as a signal, even warning, to developers that the this class must be disposed. I can’t think of any types in the framework that implement IDisposable (excluding explicit interface implementations) where disposal is optional.

Yep, that code is completely redundant and unnecessary and it doesn’t make the garbage collector do anything it wouldn’t otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. Se eu fizer isso:

 public void WasteMemory() { var instance = new MyCollection(); // this one has no Dispose() method instance.FillItWithAMillionStrings(); } // 1 million strings are in memory, but marked for reclamation by the GC 

It’s functionally identical to this for purposes of memory management:

 public void WasteMemory() { var instance = new MyCollection(); // this one has your Dispose() instance.FillItWithAMillionStrings(); instance.Dispose(); } // 1 million strings are in memory, but marked for reclamation by the GC 

If you really really really need to free the memory this very instant, call GC.Collect() . There’s no reason to do this here, though. The memory will be freed when it’s needed.

If you want to delete right now , use unmanaged memory .

Vejo:

  • Marshal.AllocHGlobal
  • Marshal.FreeHGlobal
  • Marshal.DestroyStructure

I won’t repeat the usual stuff about Using or freeing un-managed resources, that has all been covered. But I would like to point out what seems a common misconception.
Given the following code

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
   End Sub

I realise that the Disposable implementation does not follow current guidelines, but hopefully you all get the idea.
Now, when Dispose is called, how much memory gets freed?

Answer: None.
Calling Dispose can release unmanaged resources, it CANNOT reclaim managed memory, only the GC can do that. Thats not to say that the above isn’t a good idea, following the above pattern is still a good idea in fact. Once Dispose has been run, there is nothing stopping the GC re-claiming the memory that was being used by _Large, even though the instance of LargeStuff may still be in scope. The strings in _Large may also be in gen 0 but the instance of LargeStuff might be gen 2, so again, memory would be re-claimed sooner.
There is no point in adding a finaliser to call the Dispose method shown above though. That will just DELAY the re-claiming of memory to allow the finaliser to run.

In the example you posted, it still doesn’t “free the memory now”. All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You’d have to run some tests to be sure.


The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit call never happened it would then call Rollback on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

 using( MiniTx tx = new MiniTx() ) { // code that might not work. tx.Commit(); } 

I’ve also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

 using( LogTimer log = new LogTimer("MyCategory", "Some message") ) { // code to time... } 

So here are a couple of concrete examples that don’t do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.

If anything, I’d expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn’t do that if the Dispose didn’t do it…

Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I’ve written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties

There are things that the Dispose() operation does in the example code that might have an effect that would not occur due to a normal GC of the MyCollection object.

If the objects referenced by _theList or _theDict are referred to by other objects, then that List<> or Dictionary<> object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.

Of course, if this were the situation I would call it a broken design – I’m just pointing out (pedantically, I suppose) that the Dispose() operation might not be completely redundant, depending on whether there are other uses of the List<> or Dictionary<> that are not shown in the fragment.

One problem with most discussions of “unmanaged resources” is that they don’t really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn’t helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside ‘thing’ to do something on its behalf, to the detriment of some other ‘things’, and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside ‘thing’ that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the ‘thing’s usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside ‘thing’ to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside ‘thing’ if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.

IDisposable is good for unsubscribing from events.

First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn’t know how to deal with such objects. If class has for example only value types, then I don’t consider this class as class with unmanaged resources. For my code I follow next practices:

  1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
  2. Clean objects as soon as I finished usage of it.
  3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
  4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
  5. As additional precaution I try to make possible calling of Dispose() multiple times.
  6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
    Following template demonstrates what I described in words as sample of code:

 public class SomeClass : IDisposable { ///  /// As usually I don't care was object disposed or not ///  public void SomeMethod() { if (_disposed) throw new ObjectDisposedException("SomeClass instance been disposed"); } public void Dispose() { Dispose(true); } private bool _disposed; protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing)//we are in the first call { } _disposed = true; } } 

The most justifiable use case for disposal of managed resources, is preparation for the GC to reclaim resources that would otherwise never be collected.

A prime example is circular references.

Whilst it’s best practice to use patterns that avoid circular references, if you do end up with (for example) a ‘child’ object that has a reference back to its ‘parent’, this can stop GC collection of the parent if you just abandon the reference and rely on GC – plus if you have implemented a finalizer, it’ll never be called.

The only way round this is to manually break the circular references by setting the Parent references to null on the children.

Implementing IDisposable on parent and children is the best way to do this. When Dispose is called on the Parent, call Dispose on all Children, and in the child Dispose method, set the Parent references to null.

Your given code sample is not a good example for IDisposable usage. Dictionary clearing normally shouldn’t go to the Dispose method. Dictionary items will be cleared and disposed when it goes out of scope. IDisposable implementation is required to free some memory/handlers that will not release/free even after they out of scope.

The following example shows a good example for IDisposable pattern with some code and comments.

 public class DisposeExample { // A base class that implements IDisposable. // By implementing IDisposable, you are announcing that // instances of this type allocate scarce resources. public class MyResource: IDisposable { // Pointer to an external unmanaged resource. private IntPtr handle; // Other managed resource this class uses. private Component component = new Component(); // Track whether Dispose has been called. private bool disposed = false; // The class constructor. public MyResource(IntPtr handle) { this.handle = handle; } // Implement IDisposable. // Do not make this method virtual. // A derived class should not be able to override this method. public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } // Dispose(bool disposing) executes in two distinct scenarios. // If disposing equals true, the method has been called directly // or indirectly by a user's code. Managed and unmanaged resources // can be disposed. // If disposing equals false, the method has been called by the // runtime from inside the finalizer and you should not reference // other objects. Only unmanaged resources can be disposed. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if(!this.disposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if(disposing) { // Dispose managed resources. component.Dispose(); } // Call the appropriate methods to clean up // unmanaged resources here. // If disposing is false, // only the following code is executed. CloseHandle(handle); handle = IntPtr.Zero; // Note disposing has been done. disposed = true; } } // Use interop to call the method necessary // to clean up the unmanaged resource. [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle); // Use C# destructor syntax for finalization code. // This destructor will run only if the Dispose method // does not get called. // It gives your base class the opportunity to finalize. // Do not provide destructors in types derived from this class. ~MyResource() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. Dispose(false); } } public static void Main() { // Insert code here to create // and use the MyResource object. } }