Uso adequado da interface IDisposable


1659

Sei da leitura da documentação da Microsoft que o uso "primário" da IDisposableinterface é 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 MyCollectionmais 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 _theListno 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?


34
Gosto da resposta aceita, porque indica o 'padrão' correto de uso do IDisposable, mas, como o OP disse em sua edição, ele não responde à pergunta pretendida. IDisposable não 'chama' o GC, apenas 'marca' um objeto como destruível. Mas qual é a maneira real de liberar memória 'agora' em vez de esperar a entrada do GC? Eu acho que essa pergunta merece mais discussão.
Punit Vora

40
IDisposablenão marca nada. O Disposemétodo faz o que é necessário para limpar os recursos usados ​​pela instância. Isso não tem nada a ver com o GC.
John Saunders

4
@John. Eu entendo 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>. Como IDisposablenã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.
precisa saber é o seguinte

9
@desigeek: se esse for o caso, você não deveria ter dito "O IDisposable não 'chama' o GC, apenas 'marca' um objeto como destruível" #
John Saunders

5
@desigeek: Não há uma maneira garantida de liberar memória deterministicamente. Você pode chamar GC.Collect (), mas isso é uma solicitação educada, não uma demanda. Todos os threads em execução devem ser suspensos para que a coleta de lixo prossiga - leia o conceito de pontos seguros do .NET se quiser saber mais, por exemplo, msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Se um encadeamento não puder ser suspenso, por exemplo, porque há uma chamada para código não gerenciado, GC.Collect () pode não fazer nada.
Concrete Gannet

Respostas:


2609

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 tipo IntPtr, não sabe se precisa ou não chamar DeleteHandle().

Nota : O que é um recurso não gerenciado ? Se você o encontrou no Microsoft .NET Framework: ele é gerenciado. Se você foi bisbilhotar o MSDN, ele não é gerenciado. Qualquer coisa que você usou chamadas P / Invoke para sair do mundo agradável e agradável de tudo o que está disponível no .NET Framework não é gerenciada - e agora você é responsável por limpá-la.

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, existe um nome padronizado para este método:

public void Dispose()

Havia até uma interface criada IDisposable, que possui apenas esse método:

public interface IDisposable
{
   void Dispose()
}

Então, você faz seu objeto expor a IDisposableinterface e promete prometer que escreveu esse método único para limpar seus recursos não gerenciados:

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

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:

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

Então, vamos atualizar nosso Dispose()método para nos 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 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 !

Nota: Eles não vazam recursos gerenciados , porque, eventualmente, o coletor de lixo será executado em um encadeamento em segundo plano e liberará a memória associada a qualquer objeto não utilizado. Isso incluirá seu objeto e todos os objetos gerenciados que você usa (por exemplo, oe Bitmapo DbConnection).

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.

Nota: O coletor de lixo eventualmente liberará todos os objetos gerenciados. Quando isso acontece, ele chama o Finalize método no objeto. O GC não sabe nem se importa com 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 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étodo

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

~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 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á:

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

Portanto, o que você precisa é uma maneira de Finalize()dizer Dispose()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()e Dispose()chamar um terceiro método (!); de onde você passa um ditado booleano, se você está ligando para Dispose()(em oposição a Finalize()), 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):

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 IDisposable.Dispose()método 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 o seu objeto descende de um objeto implementado Dispose, não se esqueça de chamar o método Dispose de base quando você substituir o Dispose:

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

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á Disposenovamente.

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.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

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()o Dispose()método:

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

  • liberou recursos não gerenciados
  • recursos gerenciados liberados

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.Finalizediz:

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 pode 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 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:

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 IDisposable.Disposeimplementação.

Essa é a virtude de usar Disposepara 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

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


12
Você pode fazer melhor - você precisa adicionar uma chamada ao GC.SuppressFinalize () em Dispose.
plinto

55
@ Daniel Earwicker: É verdade. A Microsoft gostaria que você parasse de usar o Win32 por completo e se atenha às chamadas do .NET Framework bem abstratáveis, portáteis e independentes de dispositivos. Se você quiser dar uma olhada no sistema operacional por baixo; porque você acha que sabe o que o sistema operacional está executando: você está levando sua vida em suas próprias mãos. Nem todo aplicativo .NET está sendo executado no Windows ou em uma área de trabalho.
Ian Boyd

34
Essa é uma ótima resposta, mas acho que ela se beneficiaria de uma lista de códigos final para um caso padrão e para um caso em que a classe deriva de uma classe base que já implementa Dispose. por exemplo, tendo lido aqui ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) também fiquei confuso sobre o que devo fazer ao derivar da classe que já implementa Dispose ( ei, eu sou novo nisso).
precisa saber é o seguinte

5
@ GregS, e outros: Geralmente eu não me incomodaria em definir referências null. Primeiro de tudo, isso significa que você não pode fazê-los readonlye, em segundo lugar, é necessário fazer !=nullverificações muito feias (como no código de exemplo). Você pode ter uma bandeira disposed, mas é mais fácil não se preocupar com isso. O GC do .NET é agressivo o suficiente para que uma referência a um campo xnão seja mais contada como 'usada' no momento em que passa a x.Dispose()linha.
porges

7
Na segunda página do livro de Don Box que você mencionou, ele usa o exemplo de uma implementação O (1) do algoritmo de busca, cujos "detalhes são deixados como um exercício para o leitor". Eu ri.
Wip

65

IDisposableé frequentemente usado para explorar a usinginstrução e tirar proveito de uma maneira fácil de fazer a limpeza determinística dos 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.");
        }
    }
}

6
Pessoalmente, gosto disso, mas não combina com as diretrizes de design da estrutura.
Mqp 11/02/09

4
Eu consideraria o design adequado porque permite escopos determinísticos fáceis e construções / limpezas de escopo, especialmente quando misturados com blocos de manipulação de exceção, bloqueio e uso de recursos não gerenciados de maneiras complexas. O idioma oferece isso como um recurso de primeira classe.
Yfeldblum

Ele não segue exatamente as regras especificadas no FDG, mas certamente é um uso válido do padrão, pois é necessário para ser usado pela "declaração de uso".
11339 Scott Dorman

2
Desde que o Log.Outdent não atire, definitivamente não há nada de errado nisso.
21119 Daniel Earwicker

1
As várias respostas para É abusivo usar IDisposable e "using" como um meio de obter "comportamento com escopo definido" para segurança de exceção? entrar em um pouco mais de detalhes sobre por que pessoas diferentes gostam / não gostam dessa técnica. É um tanto controverso.
Brian

44

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:

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 na verdade é executado sob duas circunstâncias diferentes:

  • disposing == true: o método foi chamado direta ou indiretamente pelo código do 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 fazer referência a outros objetos. Somente recursos não gerenciados podem ser descartados.

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


4
Você não esqueceu de implementar o finalizador?
Budda

@ Budda: Não, ele está usando um SafeHandle. Não há necessidade de um destruidor.
Henk Holterman

9
+1 para adicionar a rede de segurança para várias chamadas para Dispose (). A especificação diz que várias chamadas devem ser seguras. Muitas classes da Microsoft falham ao implementar isso e você fica irritado com o ObjectDisposedException.
Jesse Chisholm

5
Mas Dispose (bool disposing) é seu próprio método na sua classe SimpleCleanup e nunca será chamado pela estrutura. Como você o chama apenas de "true" como parâmetro, 'descarte' nunca será falso. Seu código é muito semelhante ao exemplo do MSDN para IDisposable, mas está faltando o finalizador, como apontou @Budda, que é a origem da chamada com disposing = false.
yoyo

19

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 usinginstrução para ter a chance de garantir que ela Disposeseja 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:

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ê 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).


re: "Não deve haver mais chamadas para os métodos de um objeto depois que Dispose foi chamado nele". "Deveria" ser a palavra operativa. Se você tiver ações assíncronas pendentes, elas poderão aparecer após o descarte do seu objeto. Causando uma ObjectDisposedException.
Jesse Chisholm

A sua parece ser a única resposta que não a minha que aborda a idéia de que recursos não gerenciados encapsulam o estado que o GC não entende. Um aspecto principal de um recurso não gerenciado, no entanto, é que uma ou mais entidades cujo estado pode precisar de limpeza, podem continuar existindo mesmo se o objeto que "possui" o recurso não existir. Como você gosta da minha definição? Bem parecido, mas acho que torna o "recurso" um pouco mais substantivo (é o "acordo" do objeto externo para alterar seu comportamento, em troca da notificação de quando seus serviços não são mais necessários)
supercat

@supercat - Se você está interessado eu escrevi o post seguinte um par de dias depois que eu escrevi a resposta acima: smellegantcode.wordpress.com/2009/02/13/...
Daniel Earwicker

1
@ DanielEarwicker: artigo interessante, embora eu possa pensar em pelo menos um tipo de recurso não gerenciado que você realmente não cobre: ​​assinaturas de eventos de objetos de longa duração. As assinaturas de eventos são fungíveis, mas mesmo que a memória seja ilimitada, a falha em descartá-las pode ser cara. Por exemplo, um enumerador de uma coleção que permita modificações durante a enumeração pode precisar se inscrever para atualizar as notificações da coleção, e uma coleção pode ser atualizada várias vezes durante sua vida útil. Se os enumeradores forem abandonados sem cancelar a assinatura ... #
23813

1
O par de operações entere exité 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.)
Daniel Earwicker

17

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 ):

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
}

A explicação completa do padrão pode ser encontrada em msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ

3
nunca deve ter um finalizador incluído, a menos que você tenha recursos não gerenciados. Mesmo assim, a implementação preferida é agrupar o recurso não gerenciado no SafeHandle.
Dave Black

11

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:

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


2
re: "A memória será liberada quando necessário." Em vez disso, diga "quando a GC decidir que é necessário". Você pode ver problemas de desempenho do sistema antes que o GC decida que a memória é realmente necessária. A liberação agora pode não ser essencial, mas pode ser útil.
Jesse Chisholm

1
Existem alguns casos de esquina nos quais a anulação de referências em uma coleção pode agilizar a coleta de lixo dos itens mencionados. Por exemplo, se uma matriz grande for criada e preenchida com referências a itens menores recém-criados, mas não for necessária por muito tempo depois disso, o abandono da matriz poderá fazer com que esses itens sejam mantidos até o próximo GC de nível 2, zerá-lo primeiro pode tornar os itens elegíveis para o próximo nível 0 ou nível 1 GC. Para ter certeza, ter grandes objetos de vida curta no Large Object Heap é nojento de qualquer maneira (eu não gosto do design), mas ... #
687

1
... zerar essas matrizes antes de abandoná-las, às vezes diminuo o impacto do GC.
Supercat

11

Se MyCollectionfor 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 IDisposablefazer coisas como garantir que os threads sejam descartados corretamente, juntamente com recursos não gerenciados.

EDIT Em resposta ao comentário de Scott:

A única vez que as métricas de desempenho do GC são afetadas é quando é feita uma chamada para [sic] 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 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 IDisposablee desmontar o encadeamento duranteDispose .

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


Chamar Dispose é perfeitamente válido, legal e incentivado. Objetos que implementam IDisposable geralmente fazem isso por um motivo. A única vez que as métricas de desempenho do GC são afetadas é quando é feita uma chamada para GC.Collect ().
11339 Scott Dorman

Para muitas classes .net, o descarte é "um tanto" opcional, o que significa que abandonar instâncias "geralmente" não causará problemas, desde que não se enlouqueça ao criar novas instâncias e abandoná-las. Por exemplo, o código gerado pelo compilador para controles parece criar fontes quando os controles são instanciados e abandoná-los quando os formulários são descartados; se alguém criar e dispor de milhares de controles, isso poderá atar milhares de identificadores de GDI, mas na maioria dos casos os controles não são criados e destruídos tanto. No entanto, ainda se deve tentar evitar esse abandono.
supercat

1
No caso de fontes, suspeito que o problema é que a Microsoft nunca definiu realmente qual entidade é responsável por descartar o objeto "fonte" atribuído a um controle; em alguns casos, um controle pode compartilhar uma fonte com um objeto de vida mais longa, portanto, ter o controle Dispose a fonte seria ruim. Em outros casos, uma fonte será atribuída a um controle e em nenhum outro lugar; portanto, se o controle não o dispor, ninguém o fará. Aliás, essa dificuldade com fontes poderia ter sido evitada se houvesse uma classe FontTemplate não descartável separada, pois os controles não parecem usar o identificador GDI de sua fonte.
Supercat

Sobre o tópico de Dispose()chamadas opcionais , consulte: stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

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 Commitchamada nunca acontecesse, chamaria Rollbacka 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:

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

    tx.Commit();
} 

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.

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

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.


Veja o exemplo de @Daniel Earwicker usando funções de ordem superior. Para benchmarking, tempo, registro etc. Parece muito mais direto.
Aluan Haddad 22/09/16


6

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

Classe pública LargeStuff
  Implementa IDisposable
  Private _Large como string ()

  'Algum código estranho que significa _Large agora contém vários milhões de cadeias longas.

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

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.


1
Se uma instância de LargeStuffjá existe há tempo suficiente para chegar à Geração 2 e se _Largemantém uma referência a uma sequência recém-criada que está na Geração 0, se a instância de LargeStufffor abandonada sem anular _Large, a sequência referida por _Largeserá mantido até a próxima coleção Gen2. O zerar _Largepode 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.
Supercat

5

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


4

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


2

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 _theListou _theDictforem referidos por outros objetos, esse List<>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 usos List<>ou Dictionary<>não. mostrado no fragmento.


Eles são campos particulares, então acho justo supor que o OP não está dando referências a eles.
Mqp 11/02/09

1) o fragmento de código é apenas um exemplo de código, então estou apenas apontando que pode haver um efeito colateral fácil de ignorar; 2) campos privados geralmente são o alvo de uma propriedade / método getter - talvez demais (getter / setters são considerados por algumas pessoas como um pouco antipadrão).
227 Michael Burr

2

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.


Bem, na IMO, a definição de objeto não gerenciado é clara; qualquer objeto que não seja da GC .
Eonil

1
@Eonil: Objeto não gerenciado! = Recurso não gerenciado. Coisas como eventos podem ser implementadas inteiramente usando objetos gerenciados, mas ainda constituem recursos não gerenciados porque - pelo menos no caso de objetos de curta duração que assinam eventos de objetos de longa duração - o GC não sabe nada sobre como limpá-los .
Supercat


2

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:

  1. Se a classe criada por mim usa alguns recursos não gerenciados, significa que eu também devo implementar a interface IDisposable para limpar a memória.
  2. Limpe os objetos assim que eu terminar o uso.
  3. No meu método de disposição, eu itero sobre todos os membros IDisposable da classe e chamo Dispose.
  4. No meu método Dispose, chame GC.SuppressFinalize (this) para notificar o coletor de lixo que meu objeto já foi limpo. Eu faço isso porque ligar para o GC é uma operação cara.
  5. Como precaução adicional, tento possibilitar a chamada de Dispose () várias vezes.
  6. Às vezes, adiciono o membro privado _disposed e as chamadas de método de check-in se o objeto foi limpo. E, se foi limpo, o modelo ObjectDisposedException a
    seguir gera 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;
        }
    }

1
"Para mim, recurso não gerenciado significa alguma classe, que implementa a interface IDisposable ou algo criado com o uso de chamadas para dll." Então você está dizendo que algum tipo que is IDisposabledeveria 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.
Aluan Haddad 22/09/16

Todo mundo julga por si mesmo. Eu não gosto de adicionar algo ao meu código apenas por uma questão de adição. Isso significa que, se eu adicionar IDisposable, significa que criei algum tipo de funcionalidade que o GC não pode gerenciar ou suponho que não será capaz de gerenciar sua vida útil adequadamente.
Yuriy Zaletskyy

2

Seu exemplo de código fornecido não é um bom exemplo de IDisposableuso. A limpeza de dicionário normalmente não deve ir para o Disposemétodo Os itens do dicionário serão limpos e descartados quando ficarem fora do escopo. IDisposableA 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.

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

1

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.


4
Na maioria das vezes, o GC não funciona identificando objetos mortos, mas identificando objetos vivos. Depois que cada ciclo gc, para cada objeto registrado para finalização, é armazenado no heap de objeto grande ou é o destino de uma transmissão ao vivo 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.
Supercat

1

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.


1
ENTENDI't entendi por somehone colocar -1 a sua resposta
Sebastian Oscar Lopez
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.