Share via


Patroon verwijderen

Notitie

Deze inhoud wordt opnieuw afgedrukt door toestemming van Pearson Education, Inc. van Framework Design Guidelines: Conventions, Idioms en Patterns for Reusable .NET Libraries, 2nd Edition. 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 garbagecollection (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 de verzameling van het geheugen van het object worden uitgesteld tot de volgende ronde garbagecollection (de finalizers worden uitgevoerd tussen 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 verwijderingspatroon 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-verwijderingspatroon en geef een finalizer op typen die resources bevatten die expliciet moeten worden vrijgemaakt en die geen finalizers 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 om het Basic-verwijderingspatroon te implementeren op klassen die zelf geen onbeheerde resources of wegwerpobjecten bevatten, maar die waarschijnlijk subtypen hebben.

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 moet SuppressFinalize alleen plaatsvinden als Dispose(true) deze wordt uitgevoerd.

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

X MAAKT de methode zonder parameters Dispose niet virtueel.

De Dispose(bool) methode is de methode 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 worden aangeroepen. 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 binnen Dispose(bool) , behalve in kritieke situaties waarin het bevat proces is beschadigd (lekken, inconsistente gedeelde status, enzovoort).

Gebruikers verwachten dat een aanroep geen Dispose uitzondering genereert.

Als Dispose er een uitzondering kan worden gegenereerd, wordt verder opschoningslogica ten slotte niet uitgevoerd. Om dit te omzeilen, moet de gebruiker elke aanroep inpakken Dispose (binnen het laatste blok!) in een try-blok, wat leidt tot zeer complexe opschoonhandlers. Als u een Dispose(bool disposing) methode uitvoert, genereert u nooit een uitzondering als het verwijderen 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()te verstrekken, naast de Dispose(), als close is standaardterminologie 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);
    }
}

Af te ronden typen

Af te ronden typen zijn typen waarmee het basic-verwijderingspatroon wordt uitgebreid door de finalizer te overschrijven en het pad naar de finalisatiecode in de methode op te Dispose(bool) geven.

Finalizers zijn notoir moeilijk te implementeren correct, voornamelijk omdat u tijdens de uitvoering bepaalde (normaal 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-verwijderingspatroon betekent dit dat logica wordt Dispose(bool disposing) uitgevoerd wanneer de disposing parameter onwaar is.

Als de basisklasse al kan worden voltooid en het basic-verwijderingspatroon wordt geïmplementeerd, moet u dit niet opnieuw overschrijven Finalize . 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 definitief 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 typen af te ronden.

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. Gebruik liever resource-wrappers, zoals SafeHandle het inkapselen van onbeheerde resources, indien mogelijk, waardoor een finalizer overbodig wordt omdat de wrapper verantwoordelijk is voor het opschonen van resources.

X MAAKT waardetypen niet af te ronden.

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 definitief als het type verantwoordelijk is voor het vrijgeven van een onbeheerde resource die geen eigen finalizer heeft.

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

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

✓ IMPLEMENTeer het Basic-verwijderingspatroon op elk af te ronden 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 access any finalizable objects in the finalizer code path, because there is significant risk that will been finalized.

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 (kort van een zwakke bestelgarantie voor kritieke 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 definitief 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 waar wordt geretourneerd.

✓ 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 optreedt vanuit een finalizer, wordt het hele proces afgesloten (vanaf .NET Framework versie 2.0), waardoor andere finalizers niet op gecontroleerde wijze kunnen worden vrijgegeven en resources worden vrijgegeven.

✓ OVERWEEG om een kritiek af te ronden object (een type met een typehiërarchie die bevat CriticalFinalizerObject) te maken en te gebruiken voor situaties waarin een finalizer absoluut moet worden uitgevoerd, zelfs in het gezicht van geforceerde toepassingsdomein, worden ontlasten en threads afgebroken.

© Delen 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, published oct 22, 2008 by Addison-Wesley Professional als onderdeel van de Microsoft Windows Development Series.

Zie ook