none
Performance - Liberar manualmente objeto dentro do método?

    Question

  • Aqui na empresa o pessoal tem o costume de utilizar esse padrão de código:

        public void CopiaArray()
        {
          ArrayList meuArray;
          ArrayList segundoArray;
          try
          {
            meuArray = new ArrayList();
            segundoArray = meuArray.GetRange(0, 10);
          }
          finally
          {
            meuArray = null;
            segundoArray = null;
          }
        }
    

    Este foi somente um exemplo:

     

    Minha pergunta é o seguinte. Precisa ter o finally?

    Quando o método sair do escopo ele ja automaticamente perderá a referência, ou estou falando bobagem?

    Outra situação é que quando o objeto implementa o IDisposable além de dar um set = null no objeto antes é chamado o Dispose() do objeto.
    A pergunta é, o dispose já não é chamado assim que finaliza o método?


    Abraços, Riderman | Analista de sistemas | MCTS SQL Server http://ridermansb.spaces.live.com/
    Tuesday, August 03, 2010 3:50 AM

Answers

  • Riderman,

    Essa é engraçada... mas a resposta é NÃO!

    O Dispose só executa quando usado dentro do bloco "using". Existe uma confusão de conceitos aqui. 

    Em pascal, vc aloca memória com new() e desaloca com dispose(). Estamos falando de alocação de memória para uma classe. Parou de usar aquela memória vc desaloca ela com dispose. Em C++ é a mesma idéia com new e delete.

    Em C#, por ser uma linguagem gerenciada, não é você quem se preocupa em desalocar a memória e sim o garbage collector, logo não se "força" a desalocar memória. O máximo que vc consegue fazer é executar um GC.Collect() para forçar o garbage collector a passar (não significa ainda que ele vai desalocar, somente separar quem precisa ser coletado). Não temos muito controle sobre o comportamento do GC.

    O conceito de alocar e desalocar memória no .Net/C# não tem nada a ver com a interface IDisposable. IDisposable está relacionada com recursos "não gerenciados". Entenda eles por arquivos, handles, e qualquer outra coisa do tipo.

    IDisposable está relacionado com o bloco "using".

    Pra vc entender o comportamento é simples. Faça uma classe que implementa IDisposable:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace DisposeTests
    {
     public class DisposableClass : IDisposable
     {
      #region IDisposable Members
    
      public void Dispose()
      {
       throw new NotImplementedException();
      }
    
      #endregion
     }
    }
    

    Agora faça um form novo e jogue dois botões e o seguinte código:

      private void button1_Click(object sender, EventArgs e)
      {
       DisposableClass c = new DisposableClass();
       c = null;
      }
    
      private void button2_Click(object sender, EventArgs e)
      {
       GC.Collect();
      }

    Você vai perceber que mesmo quando roda o GC.Collect(), o método Dispose() não é executado.

    Agora altere o código do botão 1 para:

       DisposableClass c = new DisposableClass();
       using (c)
       {
        MessageBox.Show("Something");
       }

    Você vai perceber que ao término do bloco using o Disposable é executado.

    Esse comportamento é usado para orientar o desenvolvedor a não esquecer de liberar um bloco. Algumas classes que implementam esse comportamento são o StreamReader, o DataReader, justamente para ajudar o desenvolvedor a não esquecer de liberar o recurso não gerenciado (nos casos acima, arquivo e o data reader que contém internamente o handle para a conexão de banco).

    Deu pra entender agora?

    Ahhh, se a resposta foi útil, por favor, marque como resposta.

     

    Abraço,

    Eric

     

    Tuesday, August 03, 2010 1:07 PM
  • Riderman,

    No exemplo acima tem mais uma brincadeira interessante.

    Na classe DisposableClass, implemente um destrutor, com o código:

      ~DisposableClass()
      {
       MessageBox.Show("Dispose");
      }
    

    Execute várias vezes o button1. Você vai perceber q ele não cai no destrutor nenhuma vez. Isso significa que a instância de DisposableClass ainda vive na memória.

    Quando você executar o GC.Collect() vai ver o destrutor sendo chamado para cada uma das vezes que clicou no button1. E vai ver que nenhuma das vezes o Dispose foi executado.

     

    Abraço,

    Eric

    Tuesday, August 03, 2010 1:16 PM

All replies

  • Riderman,

    É bem redundante o código. O finally existe só para se acontecer alguma exceção ali dentro, setar null nas variáveis. 

    Como a variável é local, é alocada no stack, automaticamente quando o método acabar, a referência para o ArrayList vai morrer, consequentemente o Garbage Collector sabe que pode coletar aquele ArrayList (se ninguém mais guarda referência para ele).

    Se a variável "meuArray", por exemplo, estivesse num field, numa property de uma classe aí sim faria algum sentido o código acima, pois a referência seria mantida o que impediria o ArrayList de ser coletado.

    IDisposable é outra coisa. Na documentação desta interface, é citado que quando se tem uma classe gerenciada que usa um recurso não gerenciado (Ex.: Um arquivo, um handle para API do Windows...), vc precisa controlar a liberação deste recurso, então faz sua classe implementar IDisposable, para que quando ela seja liberada, o recurso seja liberado também. Geralmente IDisposable está relacionado com o uso da palavra chave "using". Sempre que vc cria um bloco using, quando ele terminar o método Dispose() de IDisposable é chamado. Um exemplo do uso disso é o StreamReader...

     

    Abraço,

    Eric

    Tuesday, August 03, 2010 11:45 AM
  • Eric, respondeu minha pergunta!  Só ficou uma dúvida.

     

    Ao final da execução do método é chamado o Dispose() para os objetos que implementem o IDisposable?


    Abraços, Riderman | Analista de sistemas | MCTS SQL Server http://ridermansb.spaces.live.com/
    Tuesday, August 03, 2010 12:05 PM
  • Riderman,

    Essa é engraçada... mas a resposta é NÃO!

    O Dispose só executa quando usado dentro do bloco "using". Existe uma confusão de conceitos aqui. 

    Em pascal, vc aloca memória com new() e desaloca com dispose(). Estamos falando de alocação de memória para uma classe. Parou de usar aquela memória vc desaloca ela com dispose. Em C++ é a mesma idéia com new e delete.

    Em C#, por ser uma linguagem gerenciada, não é você quem se preocupa em desalocar a memória e sim o garbage collector, logo não se "força" a desalocar memória. O máximo que vc consegue fazer é executar um GC.Collect() para forçar o garbage collector a passar (não significa ainda que ele vai desalocar, somente separar quem precisa ser coletado). Não temos muito controle sobre o comportamento do GC.

    O conceito de alocar e desalocar memória no .Net/C# não tem nada a ver com a interface IDisposable. IDisposable está relacionada com recursos "não gerenciados". Entenda eles por arquivos, handles, e qualquer outra coisa do tipo.

    IDisposable está relacionado com o bloco "using".

    Pra vc entender o comportamento é simples. Faça uma classe que implementa IDisposable:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace DisposeTests
    {
     public class DisposableClass : IDisposable
     {
      #region IDisposable Members
    
      public void Dispose()
      {
       throw new NotImplementedException();
      }
    
      #endregion
     }
    }
    

    Agora faça um form novo e jogue dois botões e o seguinte código:

      private void button1_Click(object sender, EventArgs e)
      {
       DisposableClass c = new DisposableClass();
       c = null;
      }
    
      private void button2_Click(object sender, EventArgs e)
      {
       GC.Collect();
      }

    Você vai perceber que mesmo quando roda o GC.Collect(), o método Dispose() não é executado.

    Agora altere o código do botão 1 para:

       DisposableClass c = new DisposableClass();
       using (c)
       {
        MessageBox.Show("Something");
       }

    Você vai perceber que ao término do bloco using o Disposable é executado.

    Esse comportamento é usado para orientar o desenvolvedor a não esquecer de liberar um bloco. Algumas classes que implementam esse comportamento são o StreamReader, o DataReader, justamente para ajudar o desenvolvedor a não esquecer de liberar o recurso não gerenciado (nos casos acima, arquivo e o data reader que contém internamente o handle para a conexão de banco).

    Deu pra entender agora?

    Ahhh, se a resposta foi útil, por favor, marque como resposta.

     

    Abraço,

    Eric

     

    Tuesday, August 03, 2010 1:07 PM
  • Riderman,

    No exemplo acima tem mais uma brincadeira interessante.

    Na classe DisposableClass, implemente um destrutor, com o código:

      ~DisposableClass()
      {
       MessageBox.Show("Dispose");
      }
    

    Execute várias vezes o button1. Você vai perceber q ele não cai no destrutor nenhuma vez. Isso significa que a instância de DisposableClass ainda vive na memória.

    Quando você executar o GC.Collect() vai ver o destrutor sendo chamado para cada uma das vezes que clicou no button1. E vai ver que nenhuma das vezes o Dispose foi executado.

     

    Abraço,

    Eric

    Tuesday, August 03, 2010 1:16 PM
  • Cara, vlw mesmo! Respondeu minha pergunta!
    Abraços, Riderman | Analista de sistemas | MCTS SQL Server http://ridermansb.spaces.live.com/
    Tuesday, August 03, 2010 1:37 PM
  • Riderman,

    instâncias de tipos valor são armazenada em uma área da memória chamada stack, onde
    o runtime pode cria-las, atualizá-las,e removê-las rapidamente com um mínimo de overhead.

    Instâncias de tipos por referência são armazenadas numa área da memória chamada heap.
    Tipos por referência armazenam o endereço do seu dado, também conhecido como um ponteiro, no stack.

    Um Boolean é um tipo por valor.
    Um ArrayList é um tipo por referência.
    Um Stream também é um tipo por referência. Mas se trata de um objeto com recurso não
    gerenciado.

    Objetos baseados em Stack são objetos do tipo valor e são armazenados no Stack e não são coletados pelo Garbage Collector por que eles são destruídos quando a stack(pilha) é
    destruída. Este ciclo de vida é muito rápido e deve ser usado para objetos de "peso leve"
    que não são mantidos por períodos maiores que uma chamada ao método.

    Objetos baseados em Heap são coletados pelo garbage collector. A limpeza da memória ou "Garbage Collection" ocorre quando o sistema não está sob carga. A maioria dos com- ponentes
    irão cair dentro desta categoria.

    Objetos com recursos não gerenciados: quando você necessita determinar que um objeto deve
    ser desalocado da memória, você pode implementar a interface IDisposable. Isto é para recursos que não são coletados e desalocados da memória pelo Garbage Collector, tais como conexões com bases de dados, datareader, streams, rpt do crystal reports, handles etc.

    No exemplo por você citado, você não deveria se preocupar com o ArrayList, já que o mesmo é um tipo por referência (heap) e será coletado pelo Garbage Collector, entretanto é de suma importância que objetos que se utilizem de recursos não gerenciados, mesmo que sejam um tipo por referência, chamem o método close().

    Conceitos:

    Stack: é uma área da memória onde os tipos valor são armazenados.
    Heap : é uma área da memória onde os tipos por referência são armazenados.
    Garbage Collection: Recuperação da memória no heap através da remoção de items referenciados.

    Espero ter sido útil no esclarecimento das tuas dúvidas.

    Atenciosamente,

    Vinícius Melo Juraszek

    Tuesday, August 03, 2010 4:22 PM
  • Vinícios, ajudou sim!

    Minha dúvida principal era, se eu tenho um objeto qualquer que está na memória heap e esse mesmo objeto foi declarado em um bloco de um método quando o método for finalizado o GC irá automaticamente limpar o objeto da memória?

    Porque se sim, a atribuição de obj = null;  é desnecessária já que essa referência irá existir apenas dentro do contexto do método.

    Ou estou enganado?

     


    Abraços, Riderman | Analista de sistemas | MCTS SQL Server http://ridermansb.spaces.live.com/
    Tuesday, August 03, 2010 4:57 PM
  • Riderman,

    Sim, será liberado e setar a variável pra null é desnecessário.

    Não instantaneamente, mas quando o GC passar.

    A questão é a "contagem de referências". Por exemplo:

    Se vc criar uma instância numa variável local ArrayList a = new ArrayList(); dentro de um método, e jogar este mesmo valor do ArrayList para uma variável privada, o arraylist não será liberado, pois ainda existirá uma referência para aquele objeto na memória e ele não será coletado. Quando a outra referência for setada para null o objeto será marcado para ser coletado pelo GC.

    No exemplo que você citou, ao término do método a variável local é desalocada do stack, por conseguinte a contagem de referências do array list cai para 0 e ele é marcado para ser coletado.

     

    Abraço,

    Eric

    Tuesday, August 03, 2010 5:10 PM
  • Exato, então entendi perfeitamente! :)

     

    Obrigado a todos!


    Abraços, Riderman | Analista de sistemas | MCTS SQL Server http://ridermansb.spaces.live.com/
    Tuesday, August 03, 2010 5:15 PM