Überprüfen von Klassenendisierern

Abgeschlossen

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 struct Typen 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 IDisposable Schnittstelle und der Dispose Methode für die deterministische Bereinigung von Ressourcen wird empfohlen. Die Dispose-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, wenn Dispose nicht aufgerufen wurde.
  • GC.SuppressFinalize(this) wird verwendet, um zu verhindern, dass der Finalizer ausgeführt wird, wenn Dispose bereits aufgerufen wurde.