Überprüfen von Klassenendisierern
Finalizer (historisch als Destruktoren bezeichnet) werden verwendet, um alle erforderlichen endgültigen Bereinigungen durchzuführen, wenn eine Klasseninstanz vom Garbage Collector (GC) erfasst wird. In den meisten Fällen können Sie das Schreiben eines Finalizers vermeiden, indem Sie die System.Runtime.InteropServices.SafeHandle oder abgeleiteten Klassen verwenden, um ein nicht verwaltetes Handle umzuschließen.
Einige Punkte, die Sie bei der Verwendung von Finalizern beachten sollten:
- Finalizer können in
structTypen nicht definiert werden. Sie werden nur für Klassen verwendet. - Eine Klasse kann nur einen Finalisierer haben.
- Finalizer können nicht geerbt oder überladen werden.
- Finalizer können nicht aufgerufen werden. Sie werden automatisch aufgerufen.
- Ein Finalizer akzeptiert keine Modifizierer oder hat Parameter.
Syntax eines Finalizers
In C# wird ein Finalizer mithilfe einer Tilde (~) gefolgt vom Klassennamen definiert. Es verwendet keine Parameter und kann nicht explizit aufgerufen werden.
class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}
Ein Finalizer kann auch als Ausdruckstextdefinition implementiert werden, wie im folgenden Beispiel gezeigt.
public class Destroyer
{
public override string ToString() => GetType().Name;
~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is executing.");
}
Beziehung zwischen Garbage Collection und Finalizern
Der Garbage Collector in .NET verwaltet automatisch die Zuordnung und Freigabe des Speichers für verwaltete Objekte. Wenn auf ein Objekt nicht mehr verwiesen wird, markiert die GC es für die Auflistung und gibt schließlich den Speicher frei. Wenn eine Klasse über einen Finalizer verfügt, ruft die GC den Finalizer auf, bevor sie den Speicher des Objekts zurückgibt. Der Finalizer ermöglicht es dem Objekt, alle nicht verwalteten Ressourcen freizugeben, die es enthält.
Der Abschlussprozess umfasst in der Regel die folgenden Schritte:
- Wenn das GC erkennt, dass ein Objekt mit einem Finalizer nicht mehr erreichbar ist, verschiebt es das Objekt in eine Finalisierungswarteschlange.
- Der Finalizerthread führt die Finalizer-Methode aus.
- Nachdem der Finalizer ausgeführt wurde, wird das Objekt in die
freachable-Warteschlange des GC verschoben, wo es für die Garbage Collection im nächsten GC-Zyklus berechtigt ist.
Beispiel für einen Finalizer
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;
}
}
}
Implementieren eines Dispose-Musters mit einem Finalizer
Schnittstellen wie IDisposable und IAsyncDisposable werden verwendet, um Ressourcen deterministisch freizugeben. Die Dispose-Methode wird explizit aufgerufen, um Ressourcen freizugeben, während der Finalizer als Sicherheitsnetz fungiert, um sicherzustellen, dass Ressourcen freigegeben werden, auch wenn Dispose nicht aufgerufen wird.
Wichtig
Das Erstellen und Verwenden von Schnittstellen geht über den Umfang dieses Moduls hinaus. Die Verwendung von IDisposable wird hier beschrieben, um Kontext für das "Dispose"-Muster bereitzustellen. Schulungen, die eine Einführung in Schnittstellen bieten, sind auf der Microsoft Learn-Plattform verfügbar.
Beachten Sie bei der Verwendung von Finalizern die folgenden Punkte:
- Finalizer sind nicht deterministisch, was bedeutet, dass Sie nicht vorhersagen können, wann der Finalizer ausgeführt wird. Es hängt vom Zeitplan der GC ab. Dieses Verhalten kann zu Verzögerungen beim Freigeben nicht verwalteter Ressourcen führen.
- Finalizer können sich auf die Leistung auswirken, da Objekte mit Finalizern länger gesammelt werden müssen.
- Die Implementierung der
IDisposableSchnittstelle und derDisposeMethode für die deterministische Bereinigung von Ressourcen wird empfohlen. DieDispose-Methode kann explizit aufgerufen werden, um Ressourcen freizugeben, und der Finalizer kann als Sicherheitsnetz verwendet werden.
Hier ist ein Beispiel für eine Klasse, die die IDisposable Schnittstelle und einen Finalizer implementiert:
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 diesem Beispiel:
- Die
Dispose-Methode wird aufgerufen, um Ressourcen deterministisch freizugeben. - Der Finalizer ruft
Dispose(false)auf, nicht verwaltete Ressourcen freizugeben, wennDisposenicht aufgerufen wurde. -
GC.SuppressFinalize(this)wird verwendet, um zu verhindern, dass der Finalizer ausgeführt wird, wennDisposebereits aufgerufen wurde.