Esaminare i finalizzatori di classe
I finalizzatori (storicamente definiti distruttori) vengono usati per eseguire qualsiasi pulizia finale necessaria quando un'istanza di classe viene raccolta dal Garbage Collector (GC). Nella maggior parte dei casi, è possibile evitare di scrivere un finalizzatore usando le classi System.Runtime.InteropServices.SafeHandle o derivate per eseguire il wrapping di qualsiasi handle non gestito.
Alcuni aspetti da tenere presenti quando si usano i finalizzatori:
- I finalizzatori non possono essere definiti nei tipi
struct. Vengono usati solo con le classi. - Una classe può avere un solo finalizzatore.
- I finalizzatori non possono essere ereditati o sovraccaricati.
- Non è possibile chiamare i finalizzatori. Vengono richiamati automaticamente.
- Un finalizzatore non accetta modificatori o ha parametri.
Sintassi di un finalizzatore
In C#, un finalizzatore viene definito usando una tilde (~) seguita dal nome della classe. Non accetta parametri e non può essere chiamato in modo esplicito.
class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}
Un finalizzatore può essere implementato anche come definizione del 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.");
}
Relazione tra Garbage Collection e finalizzatori
Il Garbage Collector in .NET gestisce automaticamente l'allocazione e il rilascio della memoria per gli oggetti gestiti. Quando non viene più fatto riferimento a un oggetto, il GC lo contrassegna per la raccolta e alla fine recupera la memoria. Se una classe dispone di un finalizzatore, il GC chiama il finalizzatore prima di recuperare la memoria dell'oggetto. Il finalizzatore consente all'oggetto di rilasciare qualsiasi risorsa non gestita che contiene.
Il processo di finalizzazione prevede in genere i passaggi seguenti:
- Quando GC rileva che un oggetto con un finalizzatore non è più raggiungibile, sposta l'oggetto in una coda di finalizzazione.
- Il thread finalizzatore esegue il metodo finalizzatore.
- Dopo l'esecuzione del finalizzatore, l'oggetto viene spostato nella coda di
freachableGC, in cui è idoneo per l'operazione di Garbage Collection nel ciclo GC successivo.
Esempio di finalizzatore
public class ResourceHolder
{
// Unmanaged resource
private IntPtr unmanagedResource;
// Constructor
public ResourceHolder()
{
// Allocate unmanaged resource
unmanagedResource = /* allocate resource */;
}
// Finalizer
~ResourceHolder()
{
// Release unmanaged resource
if (unmanagedResource != IntPtr.Zero)
{
// Free the resource
/* free resource */
unmanagedResource = IntPtr.Zero;
}
}
}
Implementare un modello dispose con un finalizzatore
Le interfacce come IDisposable e IAsyncDisposable vengono usate per rilasciare le risorse in modo deterministico. Il metodo Dispose viene chiamato in modo esplicito per rilasciare le risorse, mentre il finalizzatore funge da rete di sicurezza per garantire che le risorse vengano rilasciate anche se non viene chiamato Dispose.
Importante
La creazione e l'uso di interfacce esula dall'ambito di questo modulo. L'uso di IDisposable è descritto qui per fornire il contesto per il modello "dispose". Il training che fornisce un'introduzione alle interfacce è disponibile nella piattaforma Microsoft Learn.
Quando si usano finalizzatori, tenere presente quanto segue:
- I finalizzatori non sono deterministici, ovvero non è possibile prevedere quando viene eseguito il finalizzatore. Dipende dalla pianificazione del GC. Questo comportamento può causare ritardi nel rilascio di risorse non gestite.
- I finalizzatori possono influire sulle prestazioni perché gli oggetti con finalizzatori richiedono più tempo per essere raccolti.
- È consigliabile implementare l'interfaccia
IDisposablee il metodoDisposeper la pulizia deterministica delle risorse. Il metodoDisposepuò essere chiamato in modo esplicito per rilasciare le risorse e il finalizzatore può essere usato come rete di sicurezza.
Ecco un esempio di classe che implementa l'interfaccia IDisposable e un finalizzatore:
public class ResourceHolder : IDisposable
{
// Unmanaged resource
private IntPtr unmanagedResource;
private bool disposed = false;
// Constructor
public ResourceHolder()
{
// Allocate unmanaged resource
unmanagedResource = /* allocate resource */;
}
// Dispose method
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Free other managed objects
}
// Free unmanaged resources
if (unmanagedResource != IntPtr.Zero)
{
/* free resource */
unmanagedResource = IntPtr.Zero;
}
disposed = true;
}
}
// Finalizer
~ResourceHolder()
{
Dispose(false);
}
}
In questo esempio:
- Il metodo
Disposeviene chiamato per rilasciare le risorse in modo deterministico. - Il finalizzatore chiama
Dispose(false)per rilasciare risorse non gestite se non è stato chiamatoDispose. -
GC.SuppressFinalize(this)viene usato per impedire l'esecuzione del finalizzatore seDisposeè già stato chiamato.