Finalizer (C#-Programmierhandbuch)
Mit Finalizern (früher als Destruktoren bezeichnet) werden alle erforderlichen endgültigen Bereinigungen durchgeführt, wenn eine Klasseninstanz vom Garbage Collector gesammelt wird. In den meisten Fällen können Sie das Schreiben eines Finalizers vermeiden, indem Sie System.Runtime.InteropServices.SafeHandle oder abgeleitete Klassen verwenden, um alle nicht verwalteten Handles einzuschließen.
Hinweise
- Finalizer können nicht in Strukturen definiert werden. Sie werden nur mit Klassen verwendet.
- Eine Klasse kann nur über einen Finalizer verfügen.
- Finalizer können nicht vererbt oder überladen werden.
- Finalizer können nicht aufgerufen werden. Sie werden automatisch aufgerufen.
- Ein Finalizer kann nicht über Modifizierer oder Parameter verfügen.
Folgendes ist z.B. eine Deklaration eines Finalizers für die Klasse Car
:
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.");
}
Der Finalizer ruft Finalize implizit auf der Basisklasse des Objekts auf. Daher wird der Aufruf eines Finalizers implizit in den folgenden Code übersetzt:
protected override void Finalize()
{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}
Dies bedeutet, dass die Finalize
-Methode für alle Instanzen in der Vererbungskette rekursiv aufgerufen wird, von der am meisten bis zu der am wenigsten abgeleiteten.
Hinweis
Leere Finalizer sollten nicht verwendet werden. Wenn eine Klasse einen Finalizer enthält, wird ein Eintrag in der Finalize
-Warteschlange erstellt. Diese Warteschlange wird vom Garbage Collector verarbeitet. Während der Verarbeitung der Warteschlange ruft der GC jeden Finalizer auf. Unnötige Finalizer, darunter leere Finalizer, Finalizer, die nur den Finalizer der Basisklasse aufrufen, oder Finalizer, die nur bedingt ausgegebene Methoden aufrufen, führen zu einem unnötigen Leistungsverlust.
Der Programmierer hat keine Kontrolle darüber, wann der Finalizer aufgerufen wird, da der Garbage Collector den Zeitpunkt des Aufrufs bestimmt. Der Garbage Collector sucht nach Objekten, die von der Anwendung nicht mehr verwendet werden. Wenn er ein Objekt als abschließbar angesehen wird, ruft er den Finalizer auf (sofern vorhanden) und fordert den Arbeitsspeicher, der zum Speichern des Objekts verwendet wurde, zurück. Es ist möglich, die Garbage Collection durch Aufrufen von Collect zu erzwingen. Dieser Aufruf sollte jedoch meistens vermieden werden, da er Leistungsprobleme hervorrufen kann.
Hinweis
Ob Finalizer im Rahmen der Beendigung der Anwendung ausgeführt werden, hängt von der jeweiligen Implementierung von .NET ab. Wenn eine Anwendung beendet wird, bemüht sich .NET Framework nach Kräften, Finalizer für Objekte aufzurufen, für die noch keine Garbage Collection durchgeführt wurde, es sei denn, eine solche Bereinigung wurde unterdrückt (z. B. durch einen Aufruf der Bibliotheksmethode GC.SuppressFinalize
). .NET 5 (einschließlich .NET Core) und höhere Versionen rufen Finalizer nicht als Teil der Anwendungsbeendigung auf. Weitere Informationen finden Sie unter dem GitHub-Issue dotnet/csharpstandard #291.
Wenn beim Beenden einer Anwendung eine Bereinigung zuverlässig ausgeführt werden muss, registrieren Sie einen Handler für das Ereignis System.AppDomain.ProcessExit. Durch diesen Handler wird sichergestellt, dass IDisposable.Dispose() (oder IAsyncDisposable.DisposeAsync()) für alle Objekte aufgerufen wurde, die vor dem Beenden der Anwendung eine Bereinigung erfordern. Da Sie Finalize nicht direkt aufrufen können und nicht garantieren können, dass der Garbage Collector vor dem Beenden alle Finalizer aufruft, müssen Sie Dispose
oder DisposeAsync
verwenden, um sicherzustellen, dass Ressourcen freigegeben werden.
Verwenden von Finalizern zum Freigeben von Ressourcen
Im Allgemeinen erfordert C# nicht so viel Speicherverwaltung seitens des Entwicklers wie Sprachen, die mit der Garbage Collection nicht auf eine Laufzeit abzielen. Der Garbage Collector von .NET verwaltet implizit die Belegung und Freigabe von Arbeitsspeicher für Ihre Objekte. Wenn Ihre Anwendung nicht verwaltete Ressourcen wie z. B. Fenster, Dateien und Netzwerkverbindungen kapselt, sollten Sie Finalizer verwenden, um die Ressourcen freizugeben. Wenn das Objekt abgeschlossen werden kann, führt der Garbage Collector die Finalize
-Methode des Objekts aus.
Explizite Freigabe von Ressourcen
Wenn Ihre Anwendung eine umfangreiche externe Ressource verwendet, wird außerdem empfohlen, dass Sie eine Möglichkeit bieten, die Ressource explizit freizugeben, bevor der Garbage Collector das Objekt freigibt. Implementieren Sie zum Freigeben der Ressource eine Dispose
-Methode aus der Schnittstelle IDisposable, die die erforderliche Bereinigung für das Objekt durchführt. Dies kann die Leistung der Anwendung erheblich verbessern. Trotz dieser expliziten Kontrolle über Ressourcen wird der Finalizer Ressourcen sicher bereinigen, wenn der Aufruf der Dispose
-Methode fehlschlägt.
Weitere Informationen zum Bereinigen von Ressourcen finden Sie in den folgenden Artikeln:
- Bereinigen von nicht verwalteten Ressourcen
- Implementieren einer Dispose-Methode
- Implementieren einer DisposeAsync-Methode
using
-Anweisung
Beispiel
Das folgende Beispiel erstellt drei Klassen, die eine Vererbungskette bilden. Die Klasse First
ist die Basisklasse, Second
wird von First
abgeleitet, und Third
wird von Second
abgeleitet. Alle drei verfügen über Finalizer. In Main
wird eine Instanz der am meisten abgeleiteten Klasse erstellt. Die Ausgabe dieses Codes hängt davon ab, für welche Implementierung von .NET die Anwendung bestimmt ist:
- .NET Framework: Die Ausgabe zeigt, dass die Finalizer für die drei Klassen automatisch aufgerufen werden, wenn die Anwendung beendet wird (in der Reihenfolge von der am meisten abgeleiteten bis zur am wenigsten abgeleiteten Klasse).
- .NET 5 (einschließlich .NET Core) oder höhere Version: Es gibt keine Ausgabe, da diese Implementierung von .NET keine Finalizer aufruft, wenn die Anwendung beendet wird.
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.
*/
C#-Sprachspezifikation
Weitere Informationen finden Sie im Abschnitt Finalizer der C#-Sprachspezifikation.