Desenvolvimento - ADO.NET

Liberação de Recursos usando o Dispose Pattern

O Padrão Descarte visa padronizar o uso e implementação de finalizadores e a interface IDisposable.

por Thiago Vidal



É bastante comum nos projetos de hoje em dia encontrar uma situação na qual trabalha-se com diversos recursos na memória. Existem recursos gerenciáveis e não gerenciáveis pela máquina virtual do .NET, a Common Language Runtime (CLR) que, após a utilização, deve-se liberar a memória ocupada por esses objetos. Quando um objeto gerenciado não está mais sendo utilizado, o garbage collector libera automaticamente a memória alocada para ele, mas não é possível prever quando ocorrerá a coleta. Já os recursos não gerenciáveis, tais como conexões com banco de dados, arquivos e hardwares externos, têm de ser liberados manualmente.

E como implementar?

É necessário implementar a interface IDisposable para fazer a liberação correta do recurso, de modo que não haja risco de algum usuário desta classe acessar um recurso que já foi liberado (para não ser lançada a exceção ObjectDisposedException).

É aconselhável chamar o método Dispose apenas no lugar onde deve-se limpar o recurso.

A seguir temos as abordagens com as quais pode-se escolher uma ou outra para liberar recursos.

Try-finally

É o jeito mais antigo de consumir recursos desde que using foi implementado no .NET Framework. Uma regra básica ao se consumir um recurso é garantir o sucesso de quem for utilizá-lo. É claro que algum erro pode acontecer nessa operação, o que explica o motivo de colocar as instruções dentro de um bloco try. No bloco finally vai a chamada explícita ao método Dispose. É seguro, pois o bloco finally sempre será executado. Então, a liberação de recursos ocorre no bloco finally, como mostra a Listagem 1.

Listagem 1. Exemplo de utilização de recurso com try-finally.

  /// <summary>
  /// Cria um novo arquivo em um diretório especificado. Se o arquivo já existe,
  /// modifica-se seu nome e tenta criá-lo novamente.
  /// </summary>
  public void ExecResource()
   {
       FileStream fs = null;
       string path = @"C:\Users\Thiago\Dropbox\Teste.txt";
       try
       {
           fs = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite);
       }
       catch (IOException)
       {
           if (File.Exists(path))
           {
               var newPath = path.Replace("Teste", "outro");
               //tenta criar o arquivo com novo path
               fs = new FileStream(newPath, FileMode.CreateNew, FileAccess.ReadWrite);
           }
       }
       finally
       {
           if (fs != null)
              fs.Dispose();
       }
   }

Using

A diferença dessa abordagem é pelo fato desse bloco fazer uma chamada implícita ao método Dispose ao final de seu escopo. É necessário para a classe que utiliza o using implementar a interface IDisposable.É recomendável por ser um recurso da linguagem, dispensando uma limpeza manual.O compilador gera automaticamente os blocos try-finally e é instanciado o recurso dentro da declaração do using, como mostra a Listagem 2.

Listagem 2. Exemplo de utilização de recurso com o bloco using.

  /// <summary>
  /// Carrega um arquivo texto e copia-o em um novo arquivo
  /// </summary>
  /// <param name="path"></param>
  public void ExecResource(string path)
  {
      string destPath = @"C:\Users\Thiago\Dropbox\Teste2.txt";
   
      using (TextReader txt = new StreamReader(path))
      {
          if (File.Exists(path))
          {
              File.Copy(path, destPath);
              Console.WriteLine("Arquivo copiado com sucesso.");
          }
      }
  }

Observação: a maioria das classes do .NET Framework, como StreamReader/StreamWriter, DataTable, já implementa o método Dispose pela interface IDisposable. Nesse caso, portanto, atenha-se apenas ao uso do bloco using.

Finalizers (destrutores de objetos)

Os métodos finalizers são chamados no final do ciclo de varredura do garbage collector. Eles realizam a limpeza de recursos não gerenciados também. É uma alternativa usada quando o objeto que consome o recurso não chama o Dispose. Note que não é necessário implementar um finalizer se foi chamado o método Dispose no seu bloco finally ou se usou using implementando a interface IDisposable. Um exemplo disso é a classe BinaryWriter.

Alguns autores, como Jeffrey Richter (CLR via C#, 2010) gostam de implementar um finalizer junto com o método Dispose para ter mais controle sobre o ciclo de vida do recurso.

Implementando o Dispose Pattern

É necessário seguir os passos a seguir:

  1. Primeiro, crie uma classe que vai implementar a interface IDisposable;
  2. Crie uma flag que vai controlar se o recurso já foi liberado ou não (bool disposed);
  3. Crie um método Dispose recebendo um boolean disposing que, dependendo do valor (se for true vai liberar os recursos gerenciados e, se for false os não gerenciados). Este método deve ser protected virtual por causa da hierarquia de herança. Caso queira, defina classes derivadas que vão precisar liberar recursos não gerenciados após seu uso, na subclasse ele deverá ser sobrescrito. É assinado com protected para limitar sua visibilidade por outras classes fora da hierarquia.
    Obs: a classe base (caso haja) não deve possuir nenhum finalizer.
  4. Crie um método Dispose sem parâmetro, pois ele chamará o método Dispose(true) que, de fato, realizará as operações de liberação de recursos gerenciados e também fará uma chamada ao método SuppressFinalize do garbage collector, impedindo que ele chame o finalizer para esse objeto.
  5. Por fim, utilize o finalizer apenas se ele não foi implementado no seu código dentro de um bloco using ou try-finally com uma chamada ao Dispose.

Na Listagem 3 temos a implementação do Dipose Pattern.

Listagem 3. Implementando o Dispose Pattern.

  class ResourceExample : IDisposable
  {
      bool disposed = false;
   
      public void Dispose()
      {
          Dispose(true);
          GC.SuppressFinalize(this);
      }
   
      protected virtual void Dispose(bool disposing)
      {
          if (disposed)
            return;
   
          if (disposing)
          {
            //libera recursos gerenciados pela CLR
          }
   
          //else: libera recursos não gerenciados pela CLR
   
          disposed = true;
      }
   
      //se quiser sobrescrever (override) o finalizer, passe false no Dispose(disposing)
   
      ~ResourceExample()
      {
          Dispose(false);
      }
  }

Até a próxima.

Referências

Interface IDisposable
https://msdn.microsoft.com/pt-br/library/system.idisposable(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

CLR via C#. Jeffrey Richter. 4ª edição - Cap. 21– Automatic Memory Management (Garbage Collection)

O código completo para download está disponível aqui

Thiago Vidal

Thiago Vidal - Trabalha em Landz Tecnologia. Frequentou a PUC de São Paulo. Mora em São Paulo