Finalizzatori (Guida per programmatori C#)
I finalizzatori (detti anche distruttori) vengono usati per eseguire operazioni di pulizia finale eventualmente necessarie quando un'istanza di classe viene raccolta da Garbage Collector. Nella maggior parte dei casi, è possibile evitare di scrivere un finalizzatore usando System.Runtime.InteropServices.SafeHandle o le classi derivate o per eseguire il 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();
}
}
In questo modo, il metodo Finalize
viene chiamato in modo ricorsivo per tutte le istanze nella 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 GC elabora la coda, chiama ogni finalizzatore. I 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 a decidere 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. Sebbene sia possibile forzare l'esecuzione di Garbage Collection chiamando Collect, nella maggior parte dei casi è preferibile non effettuare questa chiamata per evitare problemi di prestazioni.
Nota
Se i finalizzatori vengono eseguiti come parte della terminazione dell'applicazione dipende da 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 di libreria GC.SuppressFinalize
). .NET 5 (incluso .NET Core) e versioni successive non chiamano finalizzatori come parte della terminazione dell'applicazione. Per altre informazioni, vedere il problema di GitHub #291 in dotnet/csharpstandard.
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 che IDisposable.Dispose() (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 i linguaggi che non hanno come destinazione un runtime con Garbage Collection. Il Garbage Collector di .NET, infatti, gestisce in modo implicito l'allocazione e il rilascio di memoria per gli oggetti. Tuttavia, quando l'applicazione incapsula risorse non gestite come finestre, file e connessioni di rete, è necessario usare i finalizzatori per rendere disponibili 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 metodo Dispose
dall'interfaccia IDisposable che esegue la pulizia necessaria per l'oggetto. Questo consente di migliorare notevolmente le prestazioni dell'applicazione. Nonostante questo controllo esplicito sulle risorse, il finalizzatore consente di salvaguardare la pulitura delle risorse nei casi in cui la chiamata al metodo Dispose
non venga eseguita correttamente.
Per altre informazioni sulla pulitura delle risorse, vedere gli articoli seguenti:
- Pulizia delle risorse non gestite
- Implementazione di un metodo Dispose
- Implementazione di un metodo DisposeAsync
- Istruzione
using
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, 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#.