Freigeben über


Dispose-Muster

Hinweis

Dieser Inhalt wird mit Genehmigung von Pearson Education, Inc. aus Framework Design Guidelines: Konventionen, Idiome und Muster für wiederverwendbare .NET-Bibliotheken, 2. Auflage nachgedruckt. Diese Ausgabe wurde 2008 veröffentlicht, und das Buch wurde seitdem in der dritten Ausgabe vollständig überarbeitet. Einige der Informationen auf dieser Seite sind möglicherweise veraltet.

Alle Programme erwerben eine oder mehrere Systemressourcen, z. B. Arbeitsspeicher, Systemhandles oder Datenbankverbindungen, während der Ausführung. Entwickler müssen bei der Verwendung solcher Systemressourcen vorsichtig sein, da sie nach dem Erwerb und der Verwendung freigegeben werden müssen.

Die CLR bietet Unterstützung für die automatische Speicherverwaltung. Verwalteter Speicher (mit dem C#-Operator newzugewiesener Speicher) muss nicht explizit freigegeben werden. Sie wird automatisch vom Garbage Collector (GC) freigegeben. Dies befreit Entwickler von der mühsamen und schwierigen Aufgabe, Arbeitsspeicher freizugeben, und ist einer der Hauptgründe für die beispiellose Produktivität, die von .NET Framework geboten wird.

Leider ist verwalteter Speicher nur eine von vielen Arten von Systemressourcen. Andere Ressourcen als verwalteter Arbeitsspeicher müssen weiterhin explizit freigegeben werden und werden als nicht verwaltete Ressourcen bezeichnet. Die GC wurde speziell nicht dazu entwickelt, solche nicht verwalteten Ressourcen zu verwalten, was bedeutet, dass die Verantwortung für die Verwaltung nicht verwalteter Ressourcen in den Händen der Entwickler liegt.

Die CLR bietet hilfe beim Freigeben nicht verwalteter Ressourcen. System.Object deklariert eine virtuelle Methode Finalize (auch als Finalizer bezeichnet), die von der GC aufgerufen wird, bevor der Speicher des Objekts vom GC beansprucht wird, und kann überschrieben werden, um nicht verwaltete Ressourcen freizugeben. Typen, die den Finalizer außer Kraft setzen, werden als finalisierbare Typen bezeichnet.

Finalizer sind zwar in einigen Bereinigungsszenarien wirksam, haben aber zwei erhebliche Nachteile:

  • Der Finalizer wird aufgerufen, wenn die GC erkennt, dass ein Objekt für die Auflistung berechtigt ist. Dies geschieht zu einem unbestimmten Zeitraum, nachdem die Ressource nicht mehr benötigt wird. Die Verzögerung zwischen dem Zeitpunkt, an dem der Entwickler die Ressource freigeben könnte oder möchte, und der Zeit, zu der die Ressource tatsächlich vom Finalizer freigegeben wird, könnte in Programmen, die viele knappe Ressourcen (Ressourcen, die leicht erschöpft werden können) benötigen, oder in Fällen, in denen es kostspielig ist, Ressourcen weiter zu nutzen (z. B. große nicht verwaltete Speicherpuffer), unakzeptabel sein.

  • Wenn die CLR einen Finalizer aufrufen muss, muss sie die Speicherbereinigung des Objekts bis zur nächsten Speicherbereinigungsrunde verschieben (die Finalizer werden zwischen den Speicherbereinigungen ausgeführt). Dies bedeutet, dass der Speicher des Objekts (und aller Objekte, auf die es verweist) über einen längeren Zeitraum nicht freigegeben wird.

Daher ist die Ausschließliche Verwendung von Finalizern in vielen Szenarien möglicherweise nicht geeignet, wenn es wichtig ist, nicht verwaltete Ressourcen so schnell wie möglich, beim Umgang mit knappen Ressourcen oder in hoch leistungsfähigen Szenarien, in denen der zusätzliche GC-Overhead der Finalisierung inakzeptabel ist, wiederzuverlangen.

Das Framework stellt die System.IDisposable Schnittstelle bereit, die implementiert werden sollte, um dem Entwickler eine manuelle Möglichkeit zum Freigeben nicht verwalteter Ressourcen bereitzustellen, sobald sie nicht benötigt werden. Es stellt außerdem die GC.SuppressFinalize Methode bereit, mit der dem GC mitgeteilt werden kann, dass ein Objekt manuell verworfen wurde und nicht mehr freigegeben werden muss. In diesem Fall kann der Speicher des Objekts früher zurückgefordert werden. Typen, die die IDisposable Schnittstelle implementieren, werden als einwegbare Typen bezeichnet.

Das Dispose-Muster soll die Verwendung und Implementierung von Finalizern und der IDisposable Schnittstelle standardisieren.

Die Hauptmotivation für das Muster besteht darin, die Komplexität der Implementierung der Finalize und Dispose Methoden zu reduzieren. Die Komplexität ergibt sich aus der Tatsache, dass die Methoden einige, aber nicht alle Codepfade teilen (die Unterschiede werden weiter unten im Kapitel beschrieben). Darüber hinaus gibt es historische Gründe für einige Elemente des Musters im Zusammenhang mit der Entwicklung der Sprachunterstützung für deterministische Ressourcenverwaltung.

✓ IMPLEMENTIEREN Sie das Basic Dispose Pattern für Typen, die Instanzen von entsorgbaren Typen enthalten. Ausführliche Informationen zum grundlegenden Muster finden Sie im Abschnitt Basic Dispose Pattern.

Wenn ein Typ für die Lebensdauer anderer einwegbarer Objekte verantwortlich ist, benötigen Entwickler auch eine Möglichkeit, sie zu entsorgen. Die Verwendung der Dispose-Methode des Containers ist eine praktische Möglichkeit, dies zu ermöglichen.

– Implementieren Sie das Basic Dispose Pattern und stellen Sie einen Finalizer für Typen bereit, die Ressourcen enthalten, die explizit freigegeben werden müssen und die nicht über Finalizer verfügen.

Beispielsweise sollte das Muster für Typen implementiert werden, die nicht verwaltete Speicherpuffer speichern. Im Abschnitt " Finalizable Types " werden Richtlinien für die Implementierung von Finalizern erläutert.

• ERWÄGEN SIE die Implementierung des Basic Dispose Pattern für Klassen, die selbst keine nicht verwalteten Ressourcen oder einwegbaren Objekte enthalten, aber wahrscheinlich Untertypen haben, die dies tun.

Ein großartiges Beispiel hierfür ist die System.IO.Stream Klasse. Obwohl es sich um eine abstrakte Basisklasse handelt, die keine Ressourcen enthält, wird das Muster von ihr implementiert, da die meisten ihrer Unterklassen Ressourcen enthalten.

Einfaches Dispose-Muster

Die grundlegende Implementierung des Musters umfasst die Implementierung der System.IDisposable Schnittstelle und das Deklarieren der Dispose(bool) Methode, die alle Ressourcenbereinigungslogik implementiert, die zwischen der Dispose Methode und dem optionalen Finalizer gemeinsam genutzt werden soll.

Das folgende Beispiel zeigt eine einfache Implementierung des grundlegenden Musters:

public class DisposableResourceHolder : IDisposable {

    private SafeHandle resource; // handle to a resource

    public DisposableResourceHolder() {
        this.resource = ... // allocates the resource
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            if (resource!= null) resource.Dispose();
        }
    }
}

Der boolesche Parameter disposing gibt an, ob die Methode aus der IDisposable.Dispose Implementierung oder aus dem Finalizer aufgerufen wurde. Die Dispose(bool) Implementierung sollte den Parameter überprüfen, bevor auf andere Referenzobjekte zugegriffen wird (z. B. das Ressourcenfeld im vorherigen Beispiel). Auf solche Objekte sollte nur zugegriffen werden, wenn die Methode aus der IDisposable.Dispose Implementierung aufgerufen wird (wenn der disposing Parameter gleich "true" ist). Wenn die Methode vom Finalizer aufgerufen wird (disposing ist falsch), sollten auf andere Objekte nicht zugegriffen werden. Der Grund dafür ist, dass Objekte in einer unvorhersehbaren Reihenfolge abgeschlossen werden, sodass sie oder eine ihrer Abhängigkeiten möglicherweise bereits abgeschlossen wurden.

Außerdem gilt dieser Abschnitt für Klassen mit einer Basis, die das Dispose-Muster noch nicht implementiert. Wenn Sie von einer Klasse erben, die das Muster bereits implementiert, überschreiben Sie einfach die Dispose(bool) Methode, um zusätzliche Ressourcenbereinigungslogik bereitzustellen.

– DEklarieren Sie eine protected virtual void Dispose(bool disposing) Methode, um alle Logik zu zentralisieren, die sich auf das Freigeben nicht verwalteter Ressourcen bezieht.

Alle Ressourcenbereinigungen sollten in dieser Methode durchgeführt werden. Die Methode wird sowohl vom Finalizer als auch von der IDisposable.Dispose Methode aufgerufen. Der Parameter ist false, wenn er von innerhalb eines Finalizers aufgerufen wird. Es sollte verwendet werden, um sicherzustellen, dass code, der während der Finalisierung ausgeführt wird, nicht auf andere finalisierbare Objekte zugreift. Details zur Implementierung von Finalizern werden im nächsten Abschnitt beschrieben.

protected virtual void Dispose(bool disposing) {
    if (disposing) {
        if (resource!= null) resource.Dispose();
    }
}

• DO implementieren Sie die IDisposable Schnittstelle, indem Sie einfach Dispose(true) aufrufen, gefolgt von GC.SuppressFinalize(this).

Der Aufruf zu SuppressFinalize sollte nur erfolgen, wenn Dispose(true) erfolgreich ausgeführt wird.

public void Dispose(){
    Dispose(true);
    GC.SuppressFinalize(this);
}

X NICHT die parameterlose Dispose Methode virtuell machen.

Die Dispose(bool) Methode ist die Methode, die von Unterklassen überschrieben werden soll.

// bad design
public class DisposableResourceHolder : IDisposable {
    public virtual void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

// good design
public class DisposableResourceHolder : IDisposable {
    public void Dispose() { ... }
    protected virtual void Dispose(bool disposing) { ... }
}

X NICHT erklären Sie keine Überladungen der Dispose Methode außer Dispose() und Dispose(bool).

Dispose sollte als reserviertes Wort betrachtet werden, um dieses Muster zu codieren und Verwirrung zwischen Implementierungen, Benutzern und Compilern zu verhindern. Einige Sprachen können dieses Muster für bestimmte Typen automatisch implementieren.

• DO ermöglicht es, die Dispose(bool) Methode mehrmals aufgerufen zu werden. Die Methode könnte nach dem ersten Aufruf möglicherweise nichts tun.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

    protected virtual void Dispose(bool disposing) {
        if (disposed) return;
        // cleanup
        ...
        disposed = true;
    }
}

X AVOID eine Ausnahme auslösen, Dispose(bool) es sei denn in kritischen Situationen, wenn der betreffende Prozess beschädigt wurde (Lecks, inkonsistenter freigegebener Zustand usw.).

Benutzer erwarten, dass ein Anruf Dispose keine Ausnahme auslöst.

Wenn Dispose eine Ausnahme auslösen könnte, wird die Aufräumlogik in weiteren finally-Blöcken nicht ausgeführt. Um dies zu umgehen, müsste der Benutzer jeden Aufruf von Dispose (innerhalb des finally-Blocks!) in einem try-Block umschließen, was zu sehr komplexen Bereinigungs-Handlern führt. Wenn Sie eine Dispose(bool disposing) Methode ausführen, lösen Sie niemals eine Ausnahme aus, wenn dispose falsch ist. Dadurch wird der Prozess beendet, wenn er innerhalb eines Finalizerkontexts ausgeführt wird.

• DO werfe ein ObjectDisposedException von einem Member, das nicht mehr verwendet werden kann, nachdem das Objekt entsorgt ist.

public class DisposableResourceHolder : IDisposable {
    bool disposed = false;
    SafeHandle resource; // handle to a resource

    public void DoSomething() {
        if (disposed) throw new ObjectDisposedException(...);
        // now call some native methods using the resource
        ...
    }
    protected virtual void Dispose(bool disposing) {
        if (disposed) return;
        // cleanup
        ...
        disposed = true;
    }
}

• ERWÄGEN SIE, neben der Close()Methode auch Dispose() bereitzustellen, sofern "Close" Standardterminologie in diesem Bereich ist.

Dabei ist es wichtig, dass Sie die Implementierung von Close identisch zu Dispose gestalten und die Methode IDisposable.Dispose ausdrücklich in Betracht ziehen.

public class Stream : IDisposable {
    IDisposable.Dispose() {
        Close();
    }
    public void Close() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Finalisierbare Typen

Finalisierbare Typen sind Typen, die das Basic Dispose Pattern erweitern, indem sie den Finalizer überschreiben und den Finalisierungscodepfad in der Dispose(bool)-Methode bereitstellen.

Finalizer sind notorisch schwierig, richtig zu implementieren, vor allem weil Sie während ihrer Ausführung keine bestimmten (normalerweise gültigen) Annahmen über den Zustand des Systems machen können. Die folgenden Leitlinien sollten sorgfältig berücksichtigt werden.

Beachten Sie, dass einige der Richtlinien nicht nur auf die Finalize Methode, sondern auf jeden Code angewendet werden, der von einem Finalizer aufgerufen wird. Im Fall des zuvor definierten Standard-Dispose-Musters bedeutet dies, dass Logik innerhalb von Dispose(bool disposing) ausgeführt wird, wenn der disposing Parameter "false" ist.

Wenn die Basisklasse bereits finalisierbar ist und das grundlegende Entsorgungsmuster implementiert, sollten Sie Finalize nicht erneut überschreiben. Stattdessen sollten Sie die Dispose(bool) Methode einfach außer Kraft setzen, um zusätzliche Ressourcenbereinigungslogik bereitzustellen.

Der folgende Code zeigt ein Beispiel für einen finalisierbaren Typ:

public class ComplexResourceHolder : IDisposable {

    private IntPtr buffer; // unmanaged memory buffer
    private SafeHandle resource; // disposable handle to a resource

    public ComplexResourceHolder() {
        this.buffer = ... // allocates memory
        this.resource = ... // allocates the resource
    }

    protected virtual void Dispose(bool disposing) {
        ReleaseBuffer(buffer); // release unmanaged memory
        if (disposing) { // release other disposable objects
            if (resource!= null) resource.Dispose();
        }
    }

    ~ComplexResourceHolder() {
        Dispose(false);
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

X VERMEIDEN, typen finalisierbar zu machen.

Berücksichtigen Sie sorgfältig jeden Fall, in dem Sie denken, dass ein Finalisierer erforderlich ist. Es gibt reale Kosten, die den Instanzen mit Finalizern zugeordnet sind, sowohl aus Leistungs- als auch aus Codekomplexitätsperspektive. Verwenden Sie nach Möglichkeit Ressourcen-Wrapper wie SafeHandle, um nicht verwaltete Ressourcen zu kapseln, wodurch ein Finalizer unnötig wird, da der Wrapper für die eigene Ressourcenbereinigung verantwortlich ist.

X DO NOT machen Sie Werttypen finalisierbar.

Nur Verweistypen werden tatsächlich vom CLR finalisiert, und daher wird jeder Versuch, einen Finalizer für einen Werttyp hinzuzufügen, ignoriert. Die C#- und C++-Compiler erzwingen diese Regel.

✓ Machen Sie einen Typ finalisierbar, wenn der Typ dafür verantwortlich ist, eine nicht verwaltete Ressource freizugeben, die nicht über einen eigenen Finalizer verfügt.

Platzieren Sie bei der Implementierung des Finalizers einfach alle Ressourcenbereinigungslogik innerhalb der Dispose(false) Methode und rufen Sie Dispose(bool disposing) auf.

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing) {
        ...
    }
}

✓ IMPLEMENTIEREN Sie das Basic Dispose Pattern für jeden finalisierbaren Typ.

Auf diese Weise können Benutzer vom Typ explizit deterministische Bereinigungen derselben Ressourcen ausführen, für die der Finalizer verantwortlich ist.

X NICHT auf finalisierbare Objekte im Finalisierungs-Codepfad zugreifen, da ein erhebliches Risiko besteht, dass sie bereits finalisiert wurden.

Beispielsweise kann ein finalisierbares Objekt A, das einen Verweis auf ein anderes finalisierbares Objekt B enthält, nicht zuverlässig B im Finalizer von A verwenden oder umgekehrt. Finalizer werden in zufälliger Reihenfolge aufgerufen (abgesehen von einer schwachen Reihenfolgegarantie für die kritische Finalisierung).

Beachten Sie außerdem, dass Objekte, die in statischen Variablen gespeichert sind, an bestimmten Punkten während des Entladens einer Anwendungsdomäne oder beim Beenden des Prozesses gesammelt werden. Der Zugriff auf eine statische Variable, die auf ein finalisierbares Objekt verweist (oder eine statische Methode aufruft, die in statischen Variablen gespeicherte Werte verwenden kann) ist möglicherweise nicht sicher, wenn Environment.HasShutdownStarted "true" zurückgegeben wird.

✓ Machen Sie Ihre Finalize Methode zu einer geschützten.

C#, C++ und VB.NET Entwickler müssen sich keine Gedanken darüber machen, da die Compiler dabei helfen, diese Richtlinie zu erzwingen.

X TUN SIE DAS NICHT lassen Sie keine Ausnahmen aus der Finalisierungslogik entkommen, außer bei systemkritischen Ausfällen.

Wenn eine Ausnahme von einem Finalizer ausgelöst wird, wird die CLR den gesamten Prozess ab .NET Framework Version 2.0 herunterfahren, wodurch verhindert wird, dass andere Finalizer ausgeführt werden und Ressourcen kontrolliert freigegeben werden.

✓ ERWÄGEN SIE, ein kritisches finalisierbares Objekt zu erstellen und zu verwenden (einen Typ mit einer Typhierarchie ) für Situationen, in denen unbedingt ein Finalizer selbst im Angesicht von erzwungenen Anwendungsdomänen-Entladungen und Threadabbrüchen ausgeführt werden muss.

© Teile 2005, 2009 Microsoft Corporation. Alle Rechte vorbehalten.

Nachdruck mit Genehmigung von Pearson Education, Inc. aus Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2. Auflage von Krzysztof Cwalina und Brad Abrams, veröffentlicht am 22. Okt 2008 von Addison-Wesley Professional als Teil der Microsoft Windows Development Series.

Siehe auch