pattern - implementando idisposable c#




Uso adequado da interface IDisposable (13)

Cenários Eu faço uso de IDisposable: limpar recursos não gerenciados, cancelar a inscrição para eventos, 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
}

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

Para mim, "não gerenciado" significa coisas como conexões de banco de dados, soquetes, identificadores de janela, etc. Mas eu vi código onde o método Dispose() é implementado para liberar recursos 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<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 a memória 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 recursos não gerenciados, como conexões de banco de dados e bitmaps. Mas suponha que _theList no código acima continha um milhão de strings, e você queria liberar essa memória agora , em vez de esperar pelo coletor de lixo. O código acima conseguiria isso?


Não deve haver mais chamadas para os métodos de um objeto após Dispose ter sido chamado (embora um objeto deva tolerar outras 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 descartar todas as referências a esse objeto inteiro (defini-las como nulas) e todos os objetos 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 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. 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 desmontagem aconteça 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 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 seguras ou não.

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

É possível pegar uma carona com o coletor de lixo para limpar seus recursos 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 recursos, 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 classe 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 (além de qualquer outra coisa, evitar IDisposable poupa o problema de ter um argumento com alguém que não resista a adicionar um finalizador a todas as classes que implementam IDisposable) .

Em vez de escrever uma aula, você escreve uma função. A função 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).


O ponto 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() 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 Framework não é gerenciado - e agora você é responsável por limpá-lo.

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, 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 objeto exponha a interface IDisposable e, dessa forma, você promete que escreveu esse método único para limpar seus recursos não gerenciados:

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

E pronto. Exceto que você pode fazer melhor.

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

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

Então agora vamos:

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

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

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

Nota: Eles não perderão recursos gerenciados , porque, eventualmente, o coletor de lixo será executado em um encadeamento de plano de fundo e liberará a memória associada a qualquer objeto não utilizado. Isso incluirá seu objeto e quaisquer objetos 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 objeto.

Nota: O coletor de lixo acabará liberando todos os objetos gerenciados. Quando isso acontecer, ele chama o método Finalize no objeto. 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 objeto pelo coletor de lixo é o momento perfeito para liberar esses recursos 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 (i.e. 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 objetos são destruídos. É inteiramente possível que no seu código Dispose() , o objeto 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 recursos 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 recursos 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 objeto desce de um objeto que implementa Dispose , então não se esqueça de chamar seu método Dispose base quando você substituir 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 objeto, 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 seu objeto tiver referências indesejadas a objetos 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 objetos que eu descartei, então eu não tente chamar Dispose em uma referência de objeto 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 objeto - seus recursos 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 recursos não gerenciados
  • recursos gerenciados liberados

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

Não consegui 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 poderia 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 irá finalizar o seu objeto. Seus recursos 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 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 implementação IDisposable.Dispose .

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

Para responder à sua pergunta original: por que não liberar memória 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:

Em 35 páginas ele explica os problemas de usar objetos 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 objetos 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 .


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 recursos 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 objeto e todas as referências a ele dos quadros de pilha dos encadeamentos. Esse heap pode ser muito grande e abranger muitas páginas de memória. 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 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. As chances são de que muitas das páginas de memória 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 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é mesmo certos não gerenciados. Somente os recursos 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á recursos efêmeros que devem ser limpos corretamente (como @fezmonkey indica 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 possui um thread (e por si próprio, quero dizer que ela criou e, portanto, é responsável por garantir que ela pare, pelo menos pelo meu estilo de codificação), então a classe provavelmente implementará IDisposablee interromperá o thread durante Dispose.

A estrutura .NET usa a IDisposableinterface como um sinal, até mesmo como aviso, para os desenvolvedores de que essa classe deve ser descartada. Não consigo pensar em nenhum tipo no framework que implementa IDisposable(excluindo implementações explícitas de interface) onde o descarte é opcional.


IDisposable é frequentemente usado para explorar a instrução using e aproveitar uma maneira fácil de fazer limpeza determinística de objetos 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 caso de uso mais justificável para o descarte de recursos gerenciados é a preparação para que o GC recupere recursos que, de outra forma, nunca seriam coletados.

Um bom exemplo é referências circulares.

Embora seja prática recomendada usar padrões que evitam referências circulares, se você acabar com (por exemplo) um objeto 'filho' que tenha uma referência de volta ao seu 'pai', isso pode parar a coleção GC do pai se você simplesmente abandonar a referência e confiar no GC - mais 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 para null nos filhos.

A implementação de IDisposable em pai e filhos é a melhor maneira de fazer isso. Quando Dispose é chamado no Parent, chame Dispose em todos os filhos e, no método Dispose do filho, defina as referências Parent como null.


Há coisas que a Dispose()operação faz no código de exemplo que pode ter um efeito que não ocorreria devido a um GC normal do MyCollectionobjeto.

Se os objetos referenciados _theListou _theDictreferenciados por outros objetos, esse List<>ou Dictionary<>objeto não estará sujeito à coleta, mas, de repente, não terá conteúdo. Se não houvesse nenhuma operação Dispose () como no exemplo, essas coleções ainda conteriam seu conteúdo.

É claro que, se essa fosse a situação, eu a chamaria de desígnio quebrado - estou apenas apontando (pedantemente, suponho) que a Dispose()operação pode não ser completamente redundante, dependendo se há outros usos do List<>ou Dictionary<>que não são mostrado no fragmento.


Não vou repetir as coisas habituais sobre como usar ou liberar recursos não gerenciados, tudo isso foi coberto. Mas gostaria de salientar o que parece ser um equívoco comum.
Dado o seguinte código

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

Percebo que a implementação de Descartáveis ​​não segue as diretrizes atuais, mas espero que todos saibam disso.
Agora, quando Dispose é chamado, quanta memória é liberada?

Resposta: nenhuma.
Chamar Dispose pode liberar recursos não gerenciados, ele NÃO PODE recuperar a memória gerenciada, somente o GC pode fazer isso. Isso não quer dizer que o acima não é uma boa idéia, seguindo o padrão acima ainda é uma boa idéia de fato. Uma vez que Dispose foi executado, não há nada que impeça o GC de reivindicar novamente a memória que estava sendo usada por _Large, embora a instância de LargeStuff ainda possa estar no escopo. As strings em _Large também podem estar no gen 0, mas a instância do LargeStuff pode ser gen 2, então, novamente, a memória seria re-reivindicada mais cedo.
Não adianta adicionar um finalizador para chamar o método Dispose mostrado acima. Isso só vai atrasar a re-reivindicação de memória para permitir que o finalizador seja executado.


Primeiro de 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 a classe tiver, por exemplo, apenas tipos de valor, não considerarei essa classe como classe com recursos não gerenciados. Para o meu código, sigo as próximas práticas:

  1. Se criado por mim, a classe usa alguns recursos não gerenciados, então isso significa que eu também deveria implementar a interface IDisposable para limpar a memória.
  2. Limpe os objetos assim que terminar de usá-los.
  3. No meu método de descarte, faço uma iteração sobre todos os membros da classe IDisposable e chamo Dispose.
  4. No meu método Dispose, chame GC.SuppressFinalize (this) para notificar o coletor de lixo que meu objeto já estava limpo. Eu faço isso porque chamar de GC é uma operação cara.
  5. Como precaução adicional, tento tornar possível a chamada de Dispose () várias vezes.
  6. Às vezes eu adiciono o membro privado _disposto e check-in chamadas de método que o objeto foi limpo. E se foi limpo, em seguida, gerar ObjectDisposedException
    seguinte modelo demonstra o que eu descrevi em palavras como amostra de código:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        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;
        }
    }

Se qualquer coisa, eu esperaria que o código seja menos eficiente do que quando deixar de fora.

Chamar os métodos Clear () é desnecessário, e o GC provavelmente não faria isso se o Dispose não fizesse isso ...


Seu exemplo de código fornecido não é um bom exemplo para IDisposableuso. A limpeza de dicionário normalmente não deveria ir para o Disposemétodo. Os itens do dicionário serão apagados e descartados quando sair do escopo. IDisposableA implementação é necessária para liberar alguma memória / manipuladores que não serão liberados / liberados mesmo depois de estarem fora do escopo.

O exemplo a seguir mostra um bom exemplo para o padrão IDisposable com algum código e comentários.

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.
    }
}

Sim, esse código é completamente redundante e desnecessário e não faz com que o coletor de lixo faça algo que não faria (uma vez que uma instância do MyCollection fica fora do escopo, isto é.) Especialmente as .Clear()chamadas.

Responda à sua edição: Mais ou menos. 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

É funcionalmente idêntico a isso para fins de gerenciamento de memória:

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

Se você realmente precisa realmente 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 for necessária.


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 encontrei sobre como o IDisposable deveria realmente ser usado.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Para a pergunta atual; Se você usar IDisposable para limpar objetos gerenciados que estão ocupando muita memória, a resposta curta seria não . A razão é que, uma vez que você descarta um IDisposable, deve deixá-lo sair do escopo. Nesse ponto, qualquer objeto filho referenciado também está fora do escopo e será coletado.

A única exceção real a isso seria se você tivesse muita memória amarrada em objetos gerenciados e bloqueasse esse encadeamento aguardando a conclusão de alguma operação. Se esses objetos não forem necessários após a conclusão da chamada, a configuração dessas referências como null poderá permitir que o coletor de lixo as colete mais cedo. Mas esse cenário representaria um código inválido que precisava ser refatorado - não um caso de uso de IDisposable.