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

Os finalizadores (historicamente chamados de 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 System.Runtime.InteropServices.SafeHandle classes ou derivadas para encapsular qualquer identificador não gerenciado.

Comentários

  • Os finalizadores não podem ser definidos em structs. Eles são usados somente com classes.
  • Uma classe pode ter somente 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 não tem parâmetros.

Por exemplo, o seguinte é uma declaração de um finalizador para a classe Car.

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

Um finalizador também pode ser implementado como uma definição do corpo da 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 implicitamente Finalize na classe base do objeto. Portanto, uma chamada para um finalizador é convertida implicitamente para o código a seguir:

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 da cadeia de herança, desde a mais derivada até a menos derivada.

Observação

Finalizadores vazios não devem ser usados. Quando uma classe contém um finalizador, uma entrada é criada na fila Finalize. 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 chamam apenas 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 procura objetos que não estão mais sendo usados pelo aplicativo. Se considerar um objeto qualificado 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.

Observação

Se os finalizadores são executados ou não como parte do encerramento do aplicativo é específico para cada implementação do .NET. Quando um aplicativo é encerrado, .NET Framework faz todos os esforços razoáveis para chamar finalizadores para objetos que ainda não foram coletados, a menos que essa limpeza tenha sido suprimida (por uma chamada ao 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 problema 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 você não pode chamar o Finalize diretamente e não pode 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.

Usar finalizadores para liberar recursos

Em geral, o C# não requer tanto gerenciamento de memória por parte do desenvolvedor quanto linguagens que não direcionam um runtime com coleta de lixo. Isso ocorre porque o coletor de lixo .NET gerencia implicitamente a alocação e a liberação de memória para seus objetos. No entanto, quando o 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 está qualificado para finalização, o coletor de lixo executa o método Finalize do objeto.

Liberação explícita de recursos

Se seu aplicativo estiver usando um recurso externo caro, também será recomendável fornecer 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 executa 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 proteção para limpar os recursos se a chamada ao 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 compõem uma cadeia de herança. A classe First é a classe base, Second é derivado de First e Third é derivado de Second. Todas as três têm finalizadores. Em Main, uma instância da classe mais derivada é criada. A saída desse código depende de qual implementação do .NET o aplicativo é direcionado:

  • .NET Framework: A saída mostra que os finalizadores das três classes são chamados automaticamente quando o aplicativo é encerrado, em 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 de Linguagem C#.

Confira também