Delen via


Patroon verwijderen

Opmerking

Deze inhoud wordt opnieuw afgedrukt met toestemming van Pearson Education, Inc. uit Framework Design Guidelines: Conventies, idioom en patronen voor herbruikbare .NET-bibliotheken, 2e editie. Die editie werd in 2008 gepubliceerd en het boek is sindsdien volledig herzien in de derde editie. Sommige informatie op deze pagina is mogelijk verouderd.

Alle programma's verkrijgen een of meer systeemresources, zoals geheugen, systeemgrepen of databaseverbindingen, tijdens de uitvoering ervan. Ontwikkelaars moeten voorzichtig zijn bij het gebruik van dergelijke systeemresources, omdat ze moeten worden vrijgegeven nadat ze zijn verkregen en gebruikt.

De CLR biedt ondersteuning voor automatisch geheugenbeheer. Beheerd geheugen (toegewezen geheugen met behulp van de C#-operator new) hoeft niet expliciet te worden vrijgegeven. Het wordt automatisch vrijgegeven door de garbage-collector (GC). Dit maakt ontwikkelaars vrij van de tijdrovende en moeilijke taak om geheugen vrij te geven en is een van de belangrijkste redenen voor de ongekende productiviteit die door het .NET Framework wordt geboden.

Het beheerde geheugen is helaas slechts een van de vele typen systeembronnen. Resources anders dan beheerd geheugen moeten nog steeds expliciet worden vrijgegeven en worden ook wel niet-beheerde resources genoemd. De GC is specifiek niet ontworpen om dergelijke onbeheerde resources te beheren, wat betekent dat de verantwoordelijkheid voor het beheren van onbeheerde resources in handen van de ontwikkelaars ligt.

De CLR biedt hulp bij het vrijgeven van onbeheerde resources. System.Object declareert een virtuele methode Finalize (ook wel de finalizer genoemd) die door de GC wordt aangeroepen voordat het geheugen van het object wordt vrijgemaakt door de GC en kan worden overschreven om onbeheerde resources vrij te geven. Typen die de finalizer overschrijven, worden aangeduid als af te ronden typen.

Hoewel finalizers effectief zijn in sommige opschoonscenario's, hebben ze twee belangrijke nadelen:

  • De finalizer wordt aangeroepen wanneer de GC detecteert dat een object in aanmerking komt voor verzameling. Dit gebeurt op een onbepaalde periode nadat de resource niet meer nodig is. De vertraging tussen wanneer de ontwikkelaar de resource zou kunnen vrijgeven of de tijd waarop de resource daadwerkelijk wordt vrijgegeven door de finalizer, kan onaanvaardbaar zijn in programma's die veel schaarse resources verkrijgen (resources die gemakkelijk kunnen worden uitgeput) of in gevallen waarin resources kostbaar zijn om in gebruik te blijven (bijvoorbeeld grote onbeheerde geheugenbuffers).

  • Wanneer de CLR een finalizer moet aanroepen, moet het verzamelen van het geheugen van het object worden uitgesteld tot de volgende ronde van vuilnisophaling (de finalizers worden uitgevoerd tussen de verzamelingen). Dit betekent dat het geheugen van het object (en alle objecten waarnaar wordt verwezen) gedurende langere tijd niet wordt vrijgegeven.

Daarom is het gebruik van uitsluitend finalizers in veel scenario's mogelijk niet geschikt wanneer het belangrijk is om onbeheerde resources zo snel mogelijk vrij te maken, bij het omgaan met schaarse resources of in zeer goed presterende scenario's waarin de toegevoegde GC-overhead van de finalisatie onaanvaardbaar is.

Het Framework biedt de System.IDisposable interface die moet worden geïmplementeerd om de ontwikkelaar een handmatige manier te bieden om onbeheerde resources vrij te geven zodra ze niet nodig zijn. Het biedt ook de GC.SuppressFinalize methode die de GC kan vertellen dat een object handmatig is verwijderd en niet meer hoeft te worden voltooid, in welk geval het geheugen van het object eerder kan worden vrijgemaakt. Typen die de IDisposable interface implementeren, worden wegwerptypen genoemd.

Het Dispose Pattern is bedoeld om het gebruik en de implementatie van finalizers en de IDisposable interface te standaardiseren.

De belangrijkste motivatie voor het patroon is het verminderen van de complexiteit van de implementatie van de Finalize en de Dispose methoden. De complexiteit komt voort uit het feit dat de methoden sommige, maar niet alle codepaden delen (de verschillen worden verderop in het hoofdstuk beschreven). Daarnaast zijn er historische redenen voor sommige elementen van het patroon met betrekking tot de ontwikkeling van taalondersteuning voor deterministisch resourcebeheer.

✓ IMPLEMENTeer het Basis verwijderingspatroon op typen die exemplaren van wegwerptypen bevatten. Zie de sectie Basic Dispose Pattern voor meer informatie over het basispatroon.

Als een type verantwoordelijk is voor de levensduur van andere wegwerpobjecten, hebben ontwikkelaars ook een manier nodig om ze te verwijderen. Het gebruik van de methode van Dispose de container is een handige manier om dit mogelijk te maken.

✓ Implementeer het Basic Dispose-patroon en geef een finalizeermethode op typen die bronnen bevatten die expliciet moeten worden vrijgemaakt en die geen finalizeermethode hebben.

Het patroon moet bijvoorbeeld worden geïmplementeerd voor typen die niet-beheerde geheugenbuffers opslaan. In de sectie Finalizable Types worden richtlijnen besproken met betrekking tot het implementeren van finalizers.

✓ OVERWEEG het Basic-verwijderingspatroon te implementeren op klassen die zelf geen onbeheerde resources of wegwerpobjecten bevatten, maar waarschijnlijk subtypen hebben die dat wel doen.

Een goed voorbeeld hiervan is de System.IO.Stream klasse. Hoewel het een abstracte basisklasse is die geen resources bevat, wordt dit patroon geïmplementeerd door de meeste subklassen.

Basispatroon verwijderen

De basisuitvoering van het patroon omvat het implementeren van de System.IDisposable interface en het declareren van de Dispose(bool) methode waarmee alle logica voor het opschonen van resources wordt geïmplementeerd die moet worden gedeeld tussen de Dispose methode en de optionele finalizer.

In het volgende voorbeeld ziet u een eenvoudige implementatie van het basispatroon:

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();
        }
    }
}

De Booleaanse parameter disposing geeft aan of de methode is aangeroepen vanuit de IDisposable.Dispose implementatie of van de finalizer. De Dispose(bool) implementatie moet de parameter controleren voordat u andere referentieobjecten opent (bijvoorbeeld het resourceveld in het voorgaande voorbeeld). Dergelijke objecten mogen alleen worden geopend wanneer de methode wordt aangeroepen vanuit de IDisposable.Dispose implementatie (wanneer de disposing parameter gelijk is aan waar). Als de methode wordt aangeroepen vanuit de finalizer (disposing onwaar), mogen andere objecten niet worden geopend. De reden hiervoor is dat objecten zijn voltooid in een onvoorspelbare volgorde, zodat ze, of een van hun afhankelijkheden, mogelijk al zijn voltooid.

Deze sectie is ook van toepassing op klassen met een basis die het verwijderingspatroon nog niet implementeert. Als u de overname uitvoert van een klasse die het patroon al implementeert, overschrijft u de Dispose(bool) methode om extra logica voor het opschonen van resources te bieden.

✓ DECLAREer een protected virtual void Dispose(bool disposing) methode voor het centraliseren van alle logica met betrekking tot het vrijgeven van onbeheerde resources.

Alle opschoning van resources moet plaatsvinden in deze methode. De methode wordt aangeroepen vanuit zowel de finalizer als de IDisposable.Dispose methode. De parameter is onwaar als deze wordt aangeroepen vanuit een finalizer. Deze moet worden gebruikt om ervoor te zorgen dat alle code die wordt uitgevoerd tijdens het voltooien geen toegang heeft tot andere voltooiende objecten. Details van het implementeren van finalizers worden beschreven in de volgende sectie.

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

✓ DO implementeer de IDisposable interface door simpelweg aan te roepen Dispose(true) gevolgd door GC.SuppressFinalize(this).

De aanroep naar SuppressFinalize moet alleen plaatsvinden als Dispose(true) succesvol wordt uitgevoerd.

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

X MAAKT de methode zonder parameters Dispose niet virtueel.

De Dispose(bool)-methode is degene die moet worden overschreven door subklassen.

// 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 DECLAREER GEEN overbelastingen van de Dispose andere methode dan Dispose() en Dispose(bool).

Dispose moet worden beschouwd als een gereserveerd woord om dit patroon te codificeren en verwarring tussen implementers, gebruikers en compilers te voorkomen. Sommige talen kunnen ervoor kiezen om dit patroon automatisch op bepaalde typen te implementeren.

✓ LAAT de Dispose(bool) methode meer dan één keer aanroepen. De methode kan ervoor kiezen om niets te doen na de eerste aanroep.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

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

X VERMIJD het genereren van een uitzondering vanuit Dispose(bool), behalve in kritieke situaties waarin het bevatte proces is beschadigd (lekken, inconsistente gedeelde toestand, enzovoort).

Gebruikers verwachten dat een aanroep van Dispose geen uitzondering veroorzaakt.

Als Dispose een uitzondering optreedt, wordt de verdere opschoningslogica vervolgens niet uitgevoerd. Om dit te omzeilen, moet de gebruiker elke aanroep naar Dispose (binnen het eindelijk blok!) inpakken in een try-blok, wat resulteert in zeer complexe opschoonhandlers. Voer een Dispose(bool disposing) methode uit, maar gooi nooit een exceptie als disposen onwaar is. Als u dit doet, wordt het proces beëindigd als het wordt uitgevoerd binnen een finalizer-context.

✓ GOOI een van elk ObjectDisposedException lid dat niet kan worden gebruikt nadat het object is verwijderd.

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;
    }
}

✓ OVERWEEG de methode Close() aan te bieden, naast de Dispose(), als "close" standaardterminologie is in het gebied.

Als u dit doet, is het belangrijk dat u de Close implementatie identiek maakt aan Dispose en de methode expliciet wilt implementeren IDisposable.Dispose .

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

Finaliseerbare types

Finaliseerbare typen zijn typen die het basis-Dispose-patroon uitbreiden door de finalizer te overschrijven en de finalisatiecodepad mogelijk te maken in de Dispose(bool) methode.

Finalizers zijn notoir moeilijk correct te implementeren, voornamelijk omdat u tijdens de uitvoering bepaalde (normaal gesproken geldige) veronderstellingen over de status van het systeem niet kunt maken. De volgende richtlijnen moeten zorgvuldig worden overwogen.

Houd er rekening mee dat sommige van de richtlijnen niet alleen van toepassing zijn op de Finalize methode, maar op code die wordt aangeroepen vanuit een finalizer. In het geval van het eerder gedefinieerde Basic Dispose Pattern betekent dit dat de logica wordt uitgevoerd Dispose(bool disposing) wanneer de disposing parameter onwaar is.

Als de basisklasse al finaliseerbaar is en het Basic-verwijderingspatroon implementeert, moet u Finalize niet opnieuw overschrijven. U moet in plaats daarvan de Dispose(bool) methode overschrijven om extra logica voor het opschonen van resources te bieden.

De volgende code toont een voorbeeld van een finaliseerbaar type:

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 Vermijd het mogelijk maken dat typen finaliseerbaar worden.

Houd zorgvuldig rekening met elk geval waarin u denkt dat er een finalizer nodig is. Er zijn echte kosten verbonden aan exemplaren met finalizers, vanuit zowel prestatie- als codecomplexiteitspunt. Indien mogelijk, gebruik liever resource-wrappers zoals SafeHandle om onbeheerde resources in te kapselen, waardoor een finalizer overbodig wordt omdat de wrapper zelf verantwoordelijk is voor het opschonen van de resources.

X NIET waardetypen finaliseerbaar maken.

Alleen referentietypen worden daadwerkelijk voltooid door de CLR, en dus elke poging om een finalizer op een waardetype te plaatsen, wordt genegeerd. Deze regel wordt afgedwongen door de C#- en C++-compilers.

✓ MAAK een type finaliseerbaar als het verantwoordelijk is voor het vrijgeven van een onbeheerde resource die geen eigen finalizer heeft.

Wanneer u de finalizer implementeert, roept u eenvoudig Dispose(false) aan en plaatst u alle logica voor het opschonen van resources in de Dispose(bool disposing) methode.

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

✓ Implementeer het Basis-verwijderingspatroon op elk finalizeerbaar type.

Dit geeft gebruikers van het type een manier om expliciet deterministische opschoning uit te voeren van dezelfde resources waarvoor de finalizer verantwoordelijk is.

X DO NOT open objecten die kunnen worden gefinaliseerd in het pad van de finalizer-code, omdat er een significant risico is dat ze al gefinaliseerd zijn.

Een definitief object A met een verwijzing naar een ander definitief object B kan bijvoorbeeld niet betrouwbaar B gebruiken in de finalizer van A, of omgekeerd. Finalizers worden in een willekeurige volgorde aangeroepen (behalve een zwakke ordegarantie voor cruciale finalisatie).

Houd er ook rekening mee dat objecten die zijn opgeslagen in statische variabelen, worden verzameld op bepaalde punten tijdens het uitladen van een toepassingsdomein of tijdens het afsluiten van het proces. Toegang tot een statische variabele die verwijst naar een finaliseerbaar object (of het aanroepen van een statische methode die mogelijk gebruikmaakt van waarden die zijn opgeslagen in statische variabelen) is mogelijk niet veilig als Environment.HasShutdownStarted het resultaat waar is.

✓ MAAK je Finalize methode beschermd.

C#, C++en VB.NET ontwikkelaars hoeven zich hier geen zorgen over te maken, omdat de compilers helpen deze richtlijn af te dwingen.

X LAAT uitzonderingen niet ontsnappen uit de finalizerlogica, met uitzondering van systeemkritieke fouten.

Als er een uitzondering wordt gegooid vanuit een finalizer, zal de CLR het gehele proces beëindigen (vanaf .NET Framework versie 2.0), waardoor andere finalizers niet kunnen worden uitgevoerd en resources niet op gecontroleerde wijze worden vrijgegeven.

✓ OVERWEEG een kritiek finaliseerbaar object (een type met een typehiërarchie die CriticalFinalizerObject bevat) te maken en te gebruiken voor situaties waarin een finalizer absoluut moet worden uitgevoerd, zelfs bij gedwongen ontladingen van toepassingsdomeinen en thread-aborties.

© Gedeelten 2005, 2009 Microsoft Corporation. Alle rechten voorbehouden.

Herdrukt door toestemming van Pearson Education, Inc. van Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition by Krzysztof Cwalina and Brad Abrams, gepubliceerd 22 oktober 2008 door Addison-Wesley Professional als onderdeel van de Microsoft Windows Development Series.

Zie ook