Quando devo criar um destruidor?

185

Por exemplo:

public class Person
{
    public Person()
    {
    }

    ~Person()
    {
    }
}

Quando devo criar manualmente um destruidor? Quando você precisou criar um destruidor?

David Heffernan
fonte
1
A linguagem C # chama esses "destruidores", mas a maioria das pessoas os chama de "finalizadores", pois esse é o nome do .NET e reduz a confusão com os destruidores do C ++ (que são bem diferentes). Como implementar IDisposable e finalizadores: 3 Regras Fácil
Stephen Cleary
5
Quando você está se sentindo imprudente.
Capdagon
32
Acho que o teclado da TomTom estava com defeito. A trava das tampas era esporadicamente ligada e desligada. Esquisito.
Jeff LaFay
veja também stackoverflow.com/questions/1076965/…
Ian Ringrose
Acabei usando um destruidor como uma ajuda de depuração com base na sugestão de Greg Beech: stackoverflow.com/questions/3832911/...
Brian

Respostas:

237

ATUALIZAÇÃO: Esta questão foi o assunto do meu blog em maio de 2015 . Obrigado pela ótima pergunta! Veja o blog para uma longa lista de falsidades nas quais as pessoas geralmente acreditam em finalização.

Quando devo criar manualmente um destruidor?

Quase nunca.

Normalmente, só se cria um destruidor quando sua classe está segurando um recurso não gerenciado caro que deve ser limpo quando o objeto desaparecer. É melhor usar o padrão descartável para garantir que o recurso seja limpo. Um destruidor é então essencialmente uma garantia de que, se o consumidor do seu objeto esquecer de descartá-lo, o recurso ainda será limpo eventualmente. (Talvez.)

Se você criar um destruidor, seja extremamente cuidadoso e entenda como o coletor de lixo funciona . Destrutores são realmente estranhos :

  • Eles não são executados no seu segmento; eles executam em seu próprio segmento. Não cause impasses!
  • Uma exceção não tratada lançada de um destruidor é uma má notícia. Está em seu próprio segmento; quem vai pegá-lo?
  • Um destruidor pode ser chamado em um objeto após o construtor iniciar, mas antes que o construtor termine. Um destruidor corretamente escrito não dependerá de invariantes estabelecidos no construtor.
  • Um destruidor pode "ressuscitar" um objeto, dando vida a um objeto morto novamente. Isso é realmente estranho. Não faça isso.
  • Um destruidor pode nunca ser executado; você não pode confiar no objeto que está sendo agendado para finalização. Ele provavelmente vai ser, mas isso não é uma garantia.

Quase nada que é normalmente verdadeiro é verdadeiro em um destruidor. Tenha muito, muito cuidado. Escrever um destruidor correto é muito difícil.

Quando você precisou criar um destruidor?

Ao testar a parte do compilador que lida com destruidores. Eu nunca precisei fazer isso no código de produção. Raramente escrevo objetos que manipulam recursos não gerenciados.

Eric Lippert
fonte
"Um destruidor pode ser chamado em um objeto após o construtor iniciar, mas antes que o construtor termine." Mas posso confiar nos inicializadores de campo para executar, certo?
configurador
13
@ configurador: Não. Suponha que o terceiro inicializador de campo de um objeto com um finalizador chamado método estático que causou uma exceção. Quando o quarto inicializador de campo seria executado? Nunca. Mas o objeto ainda está alocado e deve ser finalizado. Heck, você nem tem garantia de que os campos do tipo double foram totalmente inicializados quando o dtor é executado. Poderia ter havido um cancelamento de thread no meio da gravação do double e agora o finalizador tem que lidar com um double half-zero meio inicializado.
Eric Lippert 31/03
1
Excelente postagem, mas deveria ter dito "deve ser criado quando sua classe está segurando um objeto não gerenciado caro ou faz com que um grande número de objetos não gerenciados exista" - Para um exemplo concreto, eu tenho uma classe de matriz em C # que utiliza um C ++ nativo subjacente classe matrix para fazer muito trabalho pesado - eu faço muitas matrizes - um "destruidor" é muito superior ao IDisposable nesse caso específico, porque mantém os lados gerenciado e não gerenciado da casa em melhor sincronização
Mark Mullin
1
pythonnet usa destructor para liberar GIL em CPython não gerenciado
denfromufa
3
Artigo impressionante Eric. Adereços para isso -> "Diversão extra de bônus: o tempo de execução usa geração de código menos agressiva e coleta de lixo menos agressiva ao executar o programa no depurador, porque é uma experiência de depuração ruim ter objetos que você está depurando desaparecem repentinamente, mesmo que o A variável referente ao objeto está no escopo. Isso significa que, se você tiver um erro em que um objeto está sendo finalizado muito cedo, provavelmente não poderá reproduzir esse erro no depurador! "
11386 Ken Palmer
17

É chamado de "finalizador", e você normalmente deve criar apenas um para uma classe cujo estado (por exemplo: campos) inclua recursos não gerenciados (por exemplo: ponteiros para identificadores recuperados por meio de chamadas p / invoke). No entanto, no .NET 2.0 e posterior, há realmente uma maneira melhor de lidar com a limpeza de recursos não gerenciados: SafeHandle . Dado isso, você praticamente nunca precisará escrever um finalizador novamente.

Nicole Calinoiu
fonte
25
@ThomasEding - Sim, é . O C # usa a sintaxe do destruidor, mas na verdade está criando um finalizador . Novamente .
JDB ainda se lembra de Monica
@JDB: A construção linguística é chamada de destruidor. Não gosto do nome, mas é assim que se chama. O ato de declarar um destruidor faz com que o compilador gere um método finalizador que contenha um pouco de código de invólucro junto com o que aparecer no corpo do destruidor.
Supercat
8

Você não precisa de um, a menos que sua classe mantenha recursos não gerenciados, como identificadores de arquivo do Windows.

John Saunders
fonte
5
Bem, na verdade, ele é chamado um destruidor
David Heffernan
2
Agora estou confuso. É finalizador ou destruidor?
4
A especificação C # de fato chama isso de destruidor. Alguns vêem isso como um erro. stackoverflow.com/questions/1872700/…
Ani
2
@ThomasEding - Sim, é . O C # usa sintaxe de destruidor, mas na verdade está criando um finalizador .
JDB ainda se lembra de Monica
2
Eu amo os comentários aqui, panto verdadeira :)
Benjol
4

É chamado de destruidor / finalizador e geralmente é criado ao implementar o padrão Disposed.

É uma solução alternativa quando o usuário da sua classe se esquece de chamar Dispose, para garantir que (eventualmente) seus recursos sejam liberados, mas você não tem garantia de quando o destruidor é chamado.

Nesta questão do estouro de pilha , a resposta aceita mostra corretamente como implementar o padrão de descarte. Isso é necessário apenas se sua classe contiver recursos não manipulados que o coletor de lixo não conseguir limpar sozinho.

Uma boa prática é não implementar um finalizador sem também dar ao usuário da classe a possibilidade de Dispor manualmente o objeto para liberar os recursos imediatamente.

Øyvind Bråthen
fonte
Na verdade, NÃO é chamado de destruidor em C # por um bom motivo.
TomTom
14
Na verdade é . Obrigado por me dar um voto negativo, porque você está enganado. Consulte a biblioteca do MSDN sobre esse problema específico: msdn.microsoft.com/en-us/library/66x5fx1b.aspx
Øyvind Bråthen
1
@TomTom, seu nome oficial é destruidor
David Heffernan
Na verdade, não é um método de fallback, ele simplesmente permite que o GC gerencie quando seus objetos liberam recursos não gerenciados. A implementação do IDisposable permite que você gerencie você mesmo.
HasaniH
3

Quando você possui recursos não gerenciados e precisa garantir que eles sejam limpos quando o seu objeto desaparecer. Um bom exemplo seria objetos COM ou manipuladores de arquivos.

Vlad Bezden
fonte
2

Eu usei um destruidor (apenas para fins de depuração) para verificar se um objeto estava sendo removido da memória no escopo de um aplicativo WPF. Eu não tinha certeza se a coleta de lixo estava realmente limpando o objeto da memória, e essa era uma boa maneira de verificar.

Neil.Allen
fonte
1

Os destruidores fornecem uma maneira implícita de liberar recursos não gerenciados encapsulados em sua classe, são chamados quando o GC o aborda e chamam implicitamente o método Finalize da classe base. Se você estiver usando muitos recursos não gerenciados, é melhor fornecer uma maneira explícita de liberar esses recursos por meio da interface IDisposable. Consulte o guia de programação em C #: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

HasaniH
fonte