Finalizzatori (Guida per programmatori C#)

I finalizzatori (storicamente definiti distruttori) vengono usati per eseguire qualsiasi pulizia finale necessaria quando un'istanza di classe viene raccolta dal Garbage Collector. Nella maggior parte dei casi, è possibile evitare di scrivere un finalizzatore usando le classi derivate o per eseguire il System.Runtime.InteropServices.SafeHandle wrapping di qualsiasi handle non gestito.

Osservazioni:

  • I finalizzatori non possono essere definiti negli struct. Vengono usati solo con le classi.
  • Una classe può avere un solo finalizzatore.
  • I finalizzatori non possono essere ereditati e non è possibile eseguirne l'overload.
  • I finalizzatori non possono essere chiamati. Vengono richiamati automaticamente.
  • Un finalizzatore non accetta modificatori e non ha parametri.

Ad esempio, di seguito è riportata la dichiarazione di un finalizzatore per la classe Car.

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

Un finalizzatore può anche essere implementato come definizione di corpo dell'espressione, come illustrato nell'esempio seguente.

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

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

Il finalizzatore chiama implicitamente Finalize per la classe di base dell'oggetto. Di conseguenza, una chiamata a un finalizzatore viene convertita implicitamente nel codice seguente:

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

Questa progettazione significa che il Finalize metodo viene chiamato in modo ricorsivo per tutte le istanze della catena di ereditarietà, dalla più derivata alla meno derivata.

Nota

I finalizzatori vuoti non devono essere usati. Quando una classe contiene un finalizzatore, viene creata una voce nella coda Finalize. Questa coda viene elaborata dal Garbage Collector. Quando l'GC elabora la coda, chiama ogni finalizzatore. Finalizzatori non necessari, inclusi finalizzatori vuoti, finalizzatori che chiamano solo il finalizzatore della classe base o finalizzatori che chiamano solo metodi generati in modo condizionale, causano una perdita di prestazioni inutile.

Il programmatore non ha alcun controllo sul momento in cui viene chiamato il finalizzatore; Il Garbage Collector decide quando chiamarlo. Il Garbage Collector controlla gli oggetti che non vengono più usati dall'applicazione e, se considera un oggetto idoneo per la finalizzazione, chiama il finalizzatore (se presente) e recupera la memoria usata per archiviare l'oggetto. È possibile forzare l'operazione di Garbage Collection chiamando Collect, ma la maggior parte del tempo, questa chiamata deve essere evitata perché può creare problemi di prestazioni.

Nota

Indica se i finalizzatori vengono eseguiti come parte della terminazione dell'applicazione è specifico per ogni implementazione di .NET. Quando un'applicazione termina, .NET Framework esegue ogni ragionevole sforzo per chiamare finalizzatori per gli oggetti che non sono ancora stati sottoposti a Garbage Collection, a meno che tale pulizia non sia stata eliminata (ad esempio da una chiamata al metodo GC.SuppressFinalizedi libreria). .NET 5 (incluso .NET Core) e versioni successive non chiamano finalizzatori come parte della terminazione dell'applicazione. Per altre informazioni, vedere Problema di GitHub dotnet/csharpstandard #291.

Se è necessario eseguire la pulizia in modo affidabile quando un'applicazione viene chiusa, registrare un gestore per l'evento System.AppDomain.ProcessExit . Tale gestore garantisce IDisposable.Dispose() che (o, IAsyncDisposable.DisposeAsync()) sia stato chiamato per tutti gli oggetti che richiedono la pulizia prima dell'uscita dall'applicazione. Poiché non è possibile chiamare Finalize direttamente e non è possibile garantire che il Garbage Collector chiami tutti i finalizzatori prima dell'uscita, è necessario usare Dispose o DisposeAsync per assicurarsi che le risorse vengano liberate.

Uso di finalizzatori per liberare risorse

In generale, C# non richiede la gestione della memoria da parte dello sviluppatore come linguaggi che non hanno come destinazione un runtime con Garbage Collection. Ciò è dovuto al fatto che .NET Garbage Collector gestisce in modo implicito l'allocazione e il rilascio della memoria per gli oggetti. Tuttavia, quando l'applicazione incapsula risorse non gestite, ad esempio finestre, file e connessioni di rete, è consigliabile usare i finalizzatori per liberare tali risorse. Quando l'oggetto è idoneo per la finalizzazione, il Garbage Collector esegue il metodo Finalize dell'oggetto.

Rilascio esplicito di risorse

Se l'applicazione usa una risorsa esterna che consuma molta memoria, è consigliabile specificare un modo per rilasciare la risorsa in modo esplicito prima che il Garbage Collector renda disponibile l'oggetto. Per rilasciare la risorsa, implementare un Dispose metodo dall'interfaccia IDisposable che esegue la pulizia necessaria per l'oggetto. Questo consente di migliorare notevolmente le prestazioni dell'applicazione. Anche con questo controllo esplicito sulle risorse, il finalizzatore diventa una protezione per pulire le risorse se la chiamata al Dispose metodo non riesce.

Per altre informazioni sulla pulizia delle risorse, vedere gli articoli seguenti:

Esempio

L'esempio seguente crea tre classi che costituiscono una catena di ereditarietà. La classe First è la classe base, Second è derivata da First e Third è derivata da Second. Tutte e tre hanno finalizzatori. In Main viene creata un'istanza della classe più derivata. L'output di questo codice dipende dall'implementazione di .NET di destinazione dell'applicazione:

  • .NET Framework: l'output mostra che i finalizzatori per le tre classi vengono chiamati automaticamente quando l'applicazione termina, in ordine dal più derivato al meno derivato.
  • .NET 5 (incluso .NET Core) o versione successiva: non è disponibile alcun output, perché questa implementazione di .NET non chiama finalizzatori quando l'applicazione 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.
*/

Specifiche del linguaggio C#

Per altre informazioni, vedere la sezione Finalizzatori della specifica del linguaggio C#.

Vedi anche