Finalizar vs Dispor


Respostas:


120

Outros já abordaram a diferença entre Disposee Finalize(entre o Finalizemétodo ainda é chamado de destruidor na especificação da linguagem), então vou adicionar um pouco sobre os cenários em que o Finalizemétodo é útil.

Alguns tipos encapsulam recursos descartáveis ​​de uma maneira em que é fácil usá-los e descartá-los em uma única ação. O uso geral costuma ser assim: abrir, ler ou escrever, fechar (Descarte). Ele se encaixa muito bem com a usingconstrução.

Outros são um pouco mais difíceis. WaitEventHandlespara instâncias não são usadas assim, pois são usadas para sinalizar de um encadeamento para outro. A questão então é quem deve recorrer Disposea eles. Como uma proteção, tipos como esses implementam um Finalizemétodo, que garante que os recursos sejam descartados quando a instância não for mais referenciada pelo aplicativo.


60
Não consegui entender esta resposta aprovada. Eu ainda quero saber o diferente. O que é isso?
Ismael

22
@Ismael: A maior situação que Finalizepode ser justificada é quando há um número de objetos interessados ​​em manter um recurso vivo, mas não há como um objeto que deixa de se interessar pelo recurso possa descobrir se é o último. Nesse caso, Finalizenormalmente será acionado apenas quando ninguém estiver interessado no objeto. O tempo de folga de Finalizeé horrível para recursos não fungíveis, como arquivos e bloqueios, mas pode ser bom para recursos fungíveis.
Supercat 17/05

13
+1 a supercat para uma ótima nova palavra (para mim). O contexto deixou bem claro, mas, para o resto de nós, eis o que a Wikipedia diz: "A fungibilidade é propriedade de um bem ou de uma mercadoria cujas unidades individuais são capazes de substituição mútua, como o petróleo doce, participam uma empresa, títulos, metais preciosos ou moedas ".
Jon Coombs

5
@ JonCoombs: Isso está certo, embora possa valer a pena notar que o termo "recurso fungível" é aplicado a coisas que são livremente substituíveis até que sejam adquiridas e se tornem livremente substituíveis novamente após a liberação ou abandono . Se o sistema possui um conjunto de objetos de bloqueio e o código adquire um que ele associa a alguma entidade, desde que alguém mantenha uma referência a esse bloqueio com o objetivo de associá-lo a essa entidade , esse bloqueio não poderá ser substituído por qualquer outro. Se todo o código que se preocupa com o abandona entidade guardavam o bloqueio, no entanto, ...
supercat

... então novamente se tornaria livremente substituível até o momento em que estiver associado a alguma outra entidade.
Supercat

135

O método finalizador é chamado quando seu objeto é coletado de lixo e você não tem garantia de quando isso acontecerá (você pode forçá-lo, mas isso prejudicará o desempenho).

Por Disposeoutro lado, o método deve ser chamado pelo código que criou sua classe, para que você possa limpar e liberar todos os recursos que adquiriu (dados não gerenciados, conexões com o banco de dados, identificadores de arquivo, etc.) no momento em que o código é concluído. seu objeto.

A prática padrão é implementar IDisposablee Disposepara que você possa usar seu objeto em uma usingdeclaração. Tais como using(var foo = new MyObject()) { }. E no seu finalizador, você liga Dispose, caso o código de chamada tenha esquecido de se desfazer de você.


17
Você precisa ter um pouco de cuidado ao chamar Dispose da implementação do Finalize - Dispose também pode dispor de recursos gerenciados, que você não deseja tocar no finalizador, pois eles já podem ter sido finalizados.
itowlson

6
@itowlson: Verificar nulo combinado com a suposição de que os objetos podem ser descartados duas vezes (com a segunda chamada não fazendo nada) deve ser bom o suficiente.
Samuel

7
O padrão de IDisposal padrão e a implementação oculta de um Dispose (bool) para manipular o descarte opcional de componentes gerenciados parecem atender a esse problema.
Brody

Parece que não há razão para implementar o destruidor (o método ~ MyClass ()) e sempre implementar e chamar o método Dispose (). Ou eu estou errado? Alguém poderia me dar um exemplo quando ambos deveriam ser implementados?
precisa saber é o seguinte

66

Finalize é o método de recuo, chamado pelo coletor de lixo ao recuperar um objeto. Dispose é o método de "limpeza determinística", chamado pelos aplicativos para liberar recursos nativos valiosos (identificadores de janela, conexões com o banco de dados etc.) quando eles não são mais necessários, em vez de deixá-los retidos indefinidamente até que o GC chegue ao objeto.

Como usuário de um objeto, você sempre usa Dispose. Finalizar é para o GC.

Como implementador de uma classe, se você possui recursos gerenciados que devem ser descartados, implementa Dispose. Se você possui recursos nativos, implementa Dispose e Finalize, e ambos chamam um método comum que libera os recursos nativos. Esses idiomas geralmente são combinados por meio de um método Dispose (descarte bool) privado, que Dispose chama com true e Finalize chama com false. Esse método sempre libera recursos nativos, verifica o parâmetro de descarte e, se for verdade, descarta os recursos gerenciados e chama GC.SuppressFinalize.



2
O padrão original recomendado para classes que mantinham uma mistura de recursos de auto-limpeza ("gerenciado") e não-auto-limpeza ("não gerenciado") há muito tempo é obsoleto. Um padrão melhor é agrupar separadamente cada recurso não gerenciado em seu próprio objeto gerenciado, que não contém nenhuma referência forte a algo que não é necessário para sua limpeza. Tudo o que um objeto finalizável possui uma referência forte direta ou indireta terá sua vida útil prolongada. Encapsular as coisas necessárias para a limpeza permitirá evitar o prolongamento da vida útil da GC das coisas que não são.
Supercat 17/05

2
@ JCoombs: Disposeé bom, e implementá-lo corretamente geralmente é fácil. Finalizeé mau, e implementá-lo corretamente geralmente é difícil. Entre outras coisas, como o GC garantirá que a identidade de nenhum objeto seja "reciclada" desde que exista qualquer referência a esse objeto, é fácil limpar um monte de Disposableobjetos, alguns dos quais já podem ter sido limpos. sem problemas; qualquer referência a um objeto no qual Disposejá foi chamado permanecerá uma referência a um objeto no qual Disposejá foi chamado.
Supercat 23/10

2
@ JCoombs: Os recursos não gerenciados, por outro lado, geralmente não têm essa garantia. Se o objeto Fredpossuir o identificador de arquivo nº 42 e fechá-lo, o sistema poderá anexar esse mesmo número a alguns identificadores de arquivo que são dados a alguma outra entidade. Nesse caso, o identificador de arquivo nº 42 não se referiria ao arquivo fechado de Fred, mas ao arquivo que estava em uso ativo por essa outra entidade; para Fredtentar fechar o punho # 42 novamente seria desastroso. Tentar controlar 100% de forma confiável se um objeto não gerenciado ainda foi lançado é viável. Tentar acompanhar vários objetos é muito mais difícil.
Supercat # 23/13

2
@JCoombs: Se todo recurso não gerenciado for colocado em seu próprio objeto wrapper que não controla mais o seu tempo de vida, então código externo que não sabe se o recurso foi liberado, mas sabe que deveria ser, se ainda não o foi. , pode pedir com segurança ao objeto wrapper para liberá-lo; o objeto wrapper saberá se já fez isso e pode executar ou ignorar a solicitação. O fato de o GC garantir que uma referência ao wrapper será sempre uma referência válida ao wrapper é uma garantia muito útil .
Supercat # 23/13

43

Finalizar

  • Os finalizadores devem sempre ser protected, publicou não, privatepara que o método não possa ser chamado diretamente do código do aplicativo e, ao mesmo tempo, possa fazer uma chamada para o base.Finalizemétodo
  • Os finalizadores devem liberar apenas recursos não gerenciados.
  • A estrutura não garante que um finalizador seja executado em qualquer instância.
  • Nunca aloque memória nos finalizadores ou chame métodos virtuais dos finalizadores.
  • Evite a sincronização e crie exceções sem tratamento nos finalizadores.
  • A ordem de execução dos finalizadores é não determinística - em outras palavras, você não pode confiar em outro objeto ainda disponível no finalizador.
  • Não defina finalizadores em tipos de valor.
  • Não crie destruidores vazios. Em outras palavras, você nunca deve definir explicitamente um destruidor, a menos que sua classe precise limpar recursos não gerenciados e, se você definir um, deve fazer algum trabalho. Se, mais tarde, você não precisar mais limpar recursos não gerenciados no destruidor, remova-o completamente.

Descarte

  • Implementar IDisposableem todo tipo que tenha um finalizador
  • Verifique se um objeto está inutilizável depois de fazer uma chamada para o Disposemétodo. Em outras palavras, evite usar um objeto depois que o Disposemétodo tiver sido chamado.
  • Chame Disposetodos os IDisposabletipos assim que terminar com eles
  • Permite Disposeser chamado várias vezes sem gerar erros.
  • Suprimir chamadas posteriores para o finalizador de dentro do Disposemétodo usando o GC.SuppressFinalizemétodo
  • Evite criar tipos de valor descartáveis
  • Evite lançar exceções de dentro de Disposemétodos

Dispose / Finalized Pattern

  • A Microsoft recomenda que você implemente ambos Disposee Finalizeao trabalhar com recursos não gerenciados. A Finalizeimplementação seria executada e os recursos ainda seriam liberados quando o objeto fosse coletado como lixo, mesmo que um desenvolvedor deixasse de chamar Disposeexplicitamente o método.
  • Limpe os recursos não gerenciados no Finalizemétodo, bem como no Disposemétodo. Além disso, chame o Disposemétodo para quaisquer objetos .NET que você tenha como componentes nessa classe (tendo recursos não gerenciados como membros) a partir do Disposemétodo

17
Li essa mesma resposta em todos os lugares e ainda não consigo entender qual é o objetivo de cada uma. Eu só leio regras após regras, nada mais.
Ismael

@ Ismael: e também o autor não adiciona nada, exceto para copiar e colar algum texto do MSDN.
Tarik

@tarik Eu já aprendi. Eu tive "promessa" concepção que tempo que pedi isso.
Ismael

31

Finalize é chamado pelo GC quando este objeto não está mais em uso.

Dispose é apenas um método normal que o usuário desta classe pode chamar para liberar quaisquer recursos.

Se o usuário esqueceu de chamar Dispose e se a classe Finalize foi implementada, o GC garantirá que seja chamado.


3
Resposta mais
limpa de

19

Existem algumas chaves do livro MCSD Certification Toolkit (exame 70-483), página 193:

destruidor ≈ (é quase igual a)base.Finalize() , o destruidor é convertido em uma versão de substituição do método Finalize que executa o código do destruidor e depois chama o método Finalize da classe base. Então é totalmente não determinístico que você não pode saber quando será chamado, porque depende do GC.

Se uma classe não contém recursos gerenciados e não gerenciados , ela não deve implementar IDisposableou ter um destruidor.

Se a classe tiver apenas recursos gerenciados , ela deve ser implementada, IDisposablemas não deve ter um destruidor. (Quando o destruidor é executado, você não pode ter certeza de que os objetos gerenciados ainda existem, portanto não pode chamar os Dispose()métodos deles .)

Se a classe tiver apenas recursos não gerenciados , ela precisará ser implementada IDisposablee precisará de um destruidor, caso o programa não seja chamado Dispose().

Dispose()O método deve ser seguro para executar mais de uma vez. Você pode conseguir isso usando uma variável para acompanhar se ela já foi executada antes.

Dispose()deve liberar recursos gerenciados e não gerenciados .

O destruidor deve liberar apenas recursos não gerenciados . Quando o destruidor é executado, você não pode ter certeza de que os objetos gerenciados ainda existem; portanto, você não pode chamar os métodos Dispose de qualquer maneira. Isso é obtido usando o protected void Dispose(bool disposing)padrão canônico , onde apenas os recursos gerenciados são liberados (descartados) quando disposing == true.

Após liberar recursos, Dispose()deve chamarGC.SuppressFinalize , para que o objeto possa pular a fila de finalização.

Um exemplo de implementação para uma classe com recursos não gerenciados e gerenciados:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {
        FreeResources(true);

        // We don't need the destructor because
        // our resources are already freed.
        GC.SuppressFinalize(this);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;
        }
    }
}

2
Esta é uma boa resposta! Mas acho que isso está errado: "o destruidor deve chamar GC.SuppressFinalize". Em vez disso, o método público Dispose () não deve chamar GC.SuppressFinalize? Consulte: docs.microsoft.com/en-us/dotnet/api/… A chamada desse método evita que o coletor de lixo chame Object.Finalize (que é substituído pelo destruidor).
Ewa

7

99% do tempo, você também não deve se preocupar. :) Mas, se seus objetos mantiverem referências a recursos não gerenciados (identificadores de janela, identificadores de arquivo, por exemplo), você precisará fornecer uma maneira de seu objeto gerenciado liberar esses recursos. Finalizar fornece controle implícito sobre a liberação de recursos. É chamado pelo coletor de lixo. Dispose é uma maneira de fornecer controle explícito sobre uma liberação de recursos e pode ser chamado diretamente.

Há muito mais para aprender sobre o assunto Coleta de Lixo , mas isso é um começo.


5
Tenho certeza de que mais de 1% dos aplicativos C # usam bancos de dados: onde você precisa se preocupar com coisas de IDisposable SQL.
Samuel

1
Além disso, você deve implementar IDisposable se você encapsular IDisposables. O que provavelmente cobre os outros 1%.
9119 Darren Clark

@ Samuel: Não vejo o que os bancos de dados têm a ver com isso. Se você está falando sobre fechar conexões, tudo bem, mas isso é uma questão diferente. Você não precisa descartar objetos para fechar as conexões em tempo hábil.
JP Alioto

1
@JP: Mas o padrão Using (...) torna muito mais fácil lidar com isso.
Brody

2
Concordo, mas esse é exatamente o ponto. O padrão de uso oculta a chamada para Dispose para você.
JP Alioto

6

O finalizador é para limpeza implícita - você deve usá-lo sempre que uma classe gerenciar recursos que absolutamente devem ser limpos, caso contrário você vazaria alças / memória, etc ...

A implementação correta de um finalizador é notoriamente difícil e deve ser evitada sempre que possível - a SafeHandleclasse (disponível no .Net v2.0 e posterior) agora significa que você raramente (se alguma vez) precisa implementar mais um finalizador.

A IDisposableinterface é para limpeza explícita e é muito mais usada - você deve usar isso para permitir que os usuários liberem ou limpem explicitamente recursos sempre que terminarem de usar um objeto.

Observe que, se você tiver um finalizador, também deverá implementar a IDisposableinterface para permitir que os usuários liberem explicitamente esses recursos mais cedo do que seriam se o objeto fosse coletado com lixo.

Consulte Atualização da DG: Descarte, finalização e gerenciamento de recursos para o que considero o melhor e mais completo conjunto de recomendações sobre finalizadores e IDisposable.


3

O resumo é -

  • Você escreve um finalizador para sua classe se ele tiver referência a recursos não gerenciados e deseja garantir que esses recursos não gerenciados sejam liberados quando uma instância dessa classe for coletada de lixo automaticamente . Observe que você não pode chamar o Finalizador de um objeto explicitamente - ele é chamado automaticamente pelo coletor de lixo quando e como necessário.
  • Por outro lado, você implementa a interface IDisposable (e, consequentemente, define o método Dispose () como resultado da sua classe) quando sua classe faz referência a recursos não gerenciados, mas não deseja esperar o coletor de lixo entrar em ação (que pode estar a qualquer momento - não está no controle do programador) e deseja liberar esses recursos assim que terminar. Portanto, você pode liberar explicitamente recursos não gerenciados chamando o método Dispose () de um objeto.

Além disso, outra diferença é: na implementação Dispose (), você também deve liberar recursos gerenciados , enquanto isso não deve ser feito no Finalizador. Isso ocorre porque é muito provável que os recursos gerenciados referenciados pelo objeto já tenham sido limpos antes de estarem prontos para serem finalizados.

Para uma classe que utiliza recursos não gerenciados, a melhor prática é definir ambos - o método Dispose () e o Finalizador - a serem usados ​​como substituto no caso de um desenvolvedor esquecer de descartar explicitamente o objeto. Ambos podem usar um método compartilhado para limpar recursos gerenciados e não gerenciados:

class ClassWithDisposeAndFinalize : IDisposable
{
    // Used to determine if Dispose() has already been called, so that the finalizer
    // knows if it needs to clean up unmanaged resources.
     private bool disposed = false;

     public void Dispose()
     {
       // Call our shared helper method.
       // Specifying "true" signifies that the object user triggered the cleanup.
          CleanUp(true);

       // Now suppress finalization to make sure that the Finalize method 
       // doesn't attempt to clean up unmanaged resources.
          GC.SuppressFinalize(this);
     }
     private void CleanUp(bool disposing)
     {
        // Be sure we have not already been disposed!
        if (!this.disposed)
        {
             // If disposing equals true i.e. if disposed explicitly, dispose all 
             // managed resources.
            if (disposing)
            {
             // Dispose managed resources.
            }
             // Clean up unmanaged resources here.
        }
        disposed = true;
      }

      // the below is called the destructor or Finalizer
     ~ClassWithDisposeAndFinalize()
     {
        // Call our shared helper method.
        // Specifying "false" signifies that the GC triggered the cleanup.
        CleanUp(false);
     }

2

O melhor exemplo que eu conheço.

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

2

Diferença entre os métodos Finalize e Dispose em C #.

O GC chama o método finalize para recuperar os recursos não gerenciados (como operação de arquivos, API do Windows, conexão de rede, conexão com o banco de dados), mas o tempo não é fixo quando a GC o chamaria. É chamado implicitamente pelo GC e significa que não temos controle de baixo nível.

Método de Disposição: Temos um controle de baixo nível, como chamamos a partir do código. podemos recuperar os recursos não gerenciados sempre que acharmos que não é utilizável. Podemos conseguir isso implementando o padrão IDisposal.


1

As instâncias de classe geralmente encapsulam o controle sobre recursos que não são gerenciados pelo tempo de execução, como identificadores de janela (HWND), conexões com o banco de dados e assim por diante. Portanto, você deve fornecer uma maneira explícita e implícita de liberar esses recursos. Forneça controle implícito implementando o método Finalize protegido em um objeto (sintaxe do destruidor em C # e as extensões gerenciadas para C ++). O coletor de lixo chama esse método em algum momento depois que não há mais referências válidas para o objeto. Em alguns casos, convém fornecer aos programadores que usam um objeto a capacidade de liberar explicitamente esses recursos externos antes que o coletor de lixo libere o objeto. Se um recurso externo for escasso ou caro, um melhor desempenho poderá ser alcançado se o programador liberar explicitamente os recursos quando eles não estiverem mais sendo usados. Para fornecer controle explícito, implemente o método Dispose fornecido pela Interface IDisposable. O consumidor do objeto deve chamar esse método quando terminar de usar o objeto. Dispose pode ser chamado mesmo se outras referências ao objeto estiverem ativas.

Observe que, mesmo quando você fornece controle explícito por meio de Dispose, deve fornecer limpeza implícita usando o método Finalize. Finalizar fornece um backup para impedir que os recursos vazem permanentemente se o programador falhar em Dispose.


1

A principal diferença entre Dispose e Finalize é que:

Disposegeralmente é chamado pelo seu código. Os recursos são liberados instantaneamente quando você o chama. As pessoas esquecem de chamar o método, então a using() {}declaração é inventada. Quando o seu programa terminar a execução do código dentro {}dele, ele chamará o Disposemétodo automaticamente.

Finalizenão é chamado pelo seu código. É para ser chamado pelo Garbage Collector (GC). Isso significa que o recurso poderá ser liberado a qualquer momento no futuro, sempre que o GC decidir fazê-lo. Quando o GC faz seu trabalho, ele passa por muitos métodos Finalize. Se você tiver uma lógica pesada, isso tornará o processo lento. Isso pode causar problemas de desempenho para o seu programa. Portanto, tenha cuidado com o que você coloca lá.

Eu, pessoalmente, escreveria a maior parte da lógica de destruição em Dispose. Felizmente, isso esclarece a confusão.


-1

Como sabemos, dispor e finalizar ambos são usados ​​para liberar recursos não gerenciados.


Dispose libera o recurso imediatamente. A finalização pode ou não liberar o recurso com qualquer grau de pontualidade.
Supercat 8/15

1
Ah, ele provavelmente significa que "um objeto finalizável precisa ser detectado pelo GC duas vezes antes de recuperar sua memória", leia mais aqui: ericlippert.com/2015/05/18/…
aeroson

-4

Para responder na primeira parte, você deve fornecer exemplos em que as pessoas usam abordagens diferentes para exatamente o mesmo objeto de classe. Caso contrário, é difícil (ou até estranho) responder.

Quanto à segunda pergunta, leia melhor primeiro este uso adequado da interface IDisposable, que afirma que

É a sua escolha! Mas escolha Dispose.

Em outras palavras: o GC conhece apenas o finalizador (se houver. Também conhecido como destruidor da Microsoft). Um bom código tentará limpar os dois (finalizador e Dispose).

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.