Partilhar via


Finalizadores (Guia de Programação em C#)

Os finalizadores (historicamente referidos como destruidores) são usados para executar qualquer limpeza final necessária quando uma instância de classe está sendo coletada pelo coletor de lixo. Na maioria dos casos, você pode evitar escrever um finalizador usando as classes ou derivadas System.Runtime.InteropServices.SafeHandle para encapsular qualquer identificador não gerenciado.

Observações

  • Os finalizadores não podem ser definidos em structs. Eles são usados apenas com classes.
  • Uma aula só pode ter um finalizador.
  • Os finalizadores não podem ser herdados ou sobrecarregados.
  • Os finalizadores não podem ser chamados. Eles são invocados automaticamente.
  • Um finalizador não usa modificadores ou tem parâmetros.

Por exemplo, a seguir está uma declaração de um finalizador para a Car classe.

class Car
{
    ~Car()  // finalizer
    {
        // cleanup statements...
    }
}

Um finalizador também pode ser implementado como uma definição de corpo de expressão, como mostra o exemplo a seguir.

public class Destroyer
{
   public override string ToString() => GetType().Name;

   ~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is executing.");
}

O finalizador chama Finalize implicitamente a classe base do objeto. Portanto, uma chamada para um finalizador é implicitamente traduzida para o seguinte código:

protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}

Esse design significa que o Finalize método é chamado recursivamente para todas as instâncias na cadeia de herança, do mais derivado ao menos derivado.

Nota

Não devem ser utilizados finalizadores vazios. Quando uma classe contém um finalizador, uma entrada é criada na Finalize fila. Essa fila é processada pelo coletor de lixo. Quando o GC processa a fila, ele chama cada finalizador. Finalizadores desnecessários, incluindo finalizadores vazios, finalizadores que chamam apenas o finalizador de classe base, ou finalizadores que só chamam métodos emitidos condicionalmente, causam uma perda desnecessária de desempenho.

O programador não tem controle sobre quando o finalizador é chamado; o coletor de lixo decide quando chamá-lo. O coletor de lixo verifica se há objetos que não estão mais sendo usados pelo aplicativo. Se considerar um objeto elegível para finalização, ele chamará o finalizador (se houver) e recuperará a memória usada para armazenar o objeto. É possível forçar a coleta de lixo chamando Collect, mas na maioria das vezes, essa chamada deve ser evitada porque pode criar problemas de desempenho.

Nota

Se os finalizadores são ou não executados como parte do encerramento do aplicativo é específico para cada implementação do .NET. Quando um aplicativo é encerrado, o .NET Framework faz todos os esforços razoáveis para chamar finalizadores para objetos que ainda não foram coletados como lixo, a menos que essa limpeza tenha sido suprimida (por uma chamada para o método GC.SuppressFinalizede biblioteca, por exemplo). O .NET 5 (incluindo o .NET Core) e versões posteriores não chamam finalizadores como parte do encerramento do aplicativo. Para obter mais informações, consulte GitHub issue dotnet/csharpstandard #291.

Se você precisar executar a limpeza de forma confiável quando um aplicativo for encerrado, registre um manipulador para o System.AppDomain.ProcessExit evento. Esse manipulador garantiria IDisposable.Dispose() (ou, IAsyncDisposable.DisposeAsync()) foi chamado para todos os objetos que exigem limpeza antes da saída do aplicativo. Como não é possível chamar Finalizar diretamente e não é possível garantir que o coletor de lixo chame todos os finalizadores antes de sair, você deve usar Dispose ou DisposeAsync garantir que os recursos sejam liberados.

Usando finalizadores para liberar recursos

Em geral, o C# não requer tanto gerenciamento de memória por parte do desenvolvedor quanto as linguagens que não visam um tempo de execução com coleta de lixo. Isso ocorre porque o coletor de lixo .NET gerencia implicitamente a alocação e liberação de memória para seus objetos. No entanto, quando seu aplicativo encapsula recursos não gerenciados, como janelas, arquivos e conexões de rede, você deve usar finalizadores para liberar esses recursos. Quando o objeto é elegível para finalização, o coletor de lixo executa o Finalize método do objeto.

Liberação explícita de recursos

Se seu aplicativo estiver usando um recurso externo caro, também recomendamos que você forneça uma maneira de liberar explicitamente o recurso antes que o coletor de lixo libere o objeto. Para liberar o recurso, implemente um Dispose método da IDisposable interface que execute a limpeza necessária para o objeto. Isso pode melhorar consideravelmente o desempenho do aplicativo. Mesmo com esse controle explícito sobre os recursos, o finalizador se torna uma salvaguarda para limpar recursos se a chamada para o Dispose método falhar.

Para obter mais informações sobre como limpar recursos, consulte os seguintes artigos:

Exemplo

O exemplo a seguir cria três classes que fazem uma cadeia de herança. A classe First é a classe base, Second é derivada de First, e Third é derivada de Second. Todos os três têm finalizadores. No Main, uma instância da classe mais derivada é criada. A saída desse código depende de qual implementação do .NET o aplicativo destina:

  • .NET Framework: A saída mostra que os finalizadores para as três classes são chamados automaticamente quando o aplicativo é encerrado, na ordem do mais derivado para o menos derivado.
  • .NET 5 (incluindo o .NET Core) ou uma versão posterior: não há saída, porque essa implementação do .NET não chama finalizadores quando o aplicativo é encerrado.
class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's finalizer is called.");
    }
}

class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's finalizer is called.");
    }
}

class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's finalizer is called.");
    }
}

/* 
Test with code like the following:
    Third t = new Third();
    t = null;

When objects are finalized, the output would be:
Third's finalizer is called.
Second's finalizer is called.
First's finalizer is called.
*/

Especificação da linguagem C#

Para obter mais informações, consulte a seção Finalizadores da Especificação da linguagem C#.

Consulte também