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

Os finalizadores (historicamente chamados de destruidores) são usados para executar a limpeza final necessária quando uma instância da classe está sendo coletada pelo coletor de lixo. Na maioria dos casos, você pode evitar escrever um finalizador usando System.Runtime.InteropServices.SafeHandle ou as classes 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 arranjo significa que o método Finalize é chamado de forma recursiva para todas as instâncias da cadeia de herança, da mais derivada à 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 coletor de lixo 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 porque isso é determinado pelo coletor de lixo. 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

Executar ou não os finalizadores como parte do encerramento do aplicativo é uma situação específica 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, a menos que essa limpeza tenha sido suprimida (por uma chamada ao método de biblioteca GC.SuppressFinalize, 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 o problema do GitHub dotnet/csharpstandard #291.

Se você precisar executar a limpeza de forma confiável quando um aplicativo for encerrado, registre um manipulador para o evento System.AppDomain.ProcessExit. Esse manipulador garantiria que IDisposable.Dispose() (ou) IAsyncDisposable.DisposeAsync() fosse chamado para todos os objetos que exigem limpeza antes da saída do aplicativo. Como você não pode chamar Finalizar 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 têm como destino um runtime com coleta de lixo. Isso ocorre porque o coletor de lixo do .NET gerencia implicitamente a alocação e a 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 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 método Dispose da interface IDisposable 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 tornará uma proteção usada para limpar os recursos se a chamada para o método Dispose 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 tem como destino:

  • .NET Framework: a saída mostra que os finalizadores das 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 termina.
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, confira a seção Finalizadores da Especificação da linguagem C#.

Confira também