Megosztás a következőn keresztül:


Megsemmisítési minta

Feljegyzés

Ezt a tartalmat a Pearson Education, Inc. engedélyével nyomtatjuk újra a Framework Design Guidelines: Conventions, Idioms és Patterns for Reusable .NET Libraries, 2nd Edition engedélyével. Ezt a kiadást 2008-ban adták ki, és a könyvet azóta teljesen átdolgozták a harmadik kiadásban. Előfordulhat, hogy az oldalon található információk némelyike elavult.

Minden program egy vagy több rendszererőforrást, például memóriát, rendszerfogópontot vagy adatbázis-kapcsolatot szerez be a végrehajtás során. A fejlesztőknek óvatosnak kell lenniük az ilyen rendszererőforrások használatakor, mert a beolvasásuk és használatuk után ki kell őket szabadítani.

A CLR támogatja az automatikus memóriakezelést. A felügyelt memóriát (a C# operátorral newlefoglalt memóriát) nem kell explicit módon felszabadítani. A szemétgyűjtő (GC) automatikusan kibocsátja. Ez felszabadítja a fejlesztőket a memória felszabadításának fárasztó és nehéz feladatától, és az .NET-keretrendszer által biztosított példátlan termelékenység egyik fő oka.

Sajnos a felügyelt memória csak egyike a rendszererőforrások számos típusának. A felügyelt memórián kívüli erőforrásokat továbbra is explicit módon kell felszabadítani, és nem felügyelt erőforrásoknak nevezzük. A GC-t kifejezetten nem az ilyen nem felügyelt erőforrások kezelésére tervezték, ami azt jelenti, hogy a nem felügyelt erőforrások kezelésének felelőssége a fejlesztők kezében van.

A CLR segítséget nyújt a nem felügyelt erőforrások kiadásában. System.Object Deklarál egy virtuális metódust Finalize (más néven véglegesítőt), amelyet a GC hív meg, mielőtt az objektum memóriáját a GC visszanyerné, és felül lehet bírálni a nem felügyelt erőforrások felszabadításához. A véglegesítőt felülbíráló típusokat véglegesíthető típusoknak nevezzük.

Bár a véglegesítők bizonyos tisztítási forgatókönyvekben hatékonyak, két jelentős hátrányuk van:

  • A véglegesítő akkor lesz meghívva, ha a csoportházirend-objektum észleli, hogy egy objektum jogosult a gyűjteményre. Ez egy meghatározatlan időpontban történik, miután az erőforrásra már nincs szükség. A sok szűkös erőforrást (könnyen kimeríthető erőforrásokat) beszerező programokban, illetve olyan esetekben, amikor az erőforrások használatbavétele költséges (például nagy, nem felügyelt memóriapufferek) a fejlesztő által felszabadítható vagy felszabadítható időpont között elfogadhatatlan lehet.

  • Amikor a CLR-nek véglegesítőt kell hívnia, el kell halasztania az objektum memóriájának gyűjtését a szemétgyűjtés következő fordulójáig (a véglegesítők a gyűjtemények között futnak). Ez azt jelenti, hogy az objektum memóriája (és az általa hivatkozott összes objektum) hosszabb ideig nem lesz felszabadítva.

Ezért előfordulhat, hogy a kizárólag véglegesítőkre hagyatkozás sok esetben nem megfelelő, ha fontos a nem felügyelt erőforrások lehető leggyorsabban történő visszaigénylése, a szűkös erőforrások kezelésekor, vagy olyan nagy teljesítményű forgatókönyvekben, amelyekben a véglegesítés hozzáadott GC-többlettere elfogadhatatlan.

A keretrendszer biztosítja a System.IDisposable implementálandó felületet, amely lehetővé teszi a fejlesztő számára a nem felügyelt erőforrások manuális kiadását, amint nincs rájuk szükség. Ez a módszer azt is lehetővé teszi GC.SuppressFinalize , hogy a GC megállapítsa, hogy egy objektumot manuálisan ártalmatlanítottak, és nem kell többé véglegesíteni, ebben az esetben az objektum memóriája korábban visszanyerhető. Az interfészt IDisposable megvalósító típusokat eldobható típusoknak nevezzük.

Az Elidegenítési minta a véglegesítők és az interfész használatának és implementálásának szabványosítására IDisposable szolgál.

A minta fő motivációja az, hogy csökkentse a végrehajtás összetettségét Finalize és a Dispose módszereket. Az összetettség abból ered, hogy a metódusok néhány, de nem minden kódútvonalon osztoznak (a különbségeket a fejezet későbbi részében ismertetjük). Emellett a minta egyes elemeinek történelmi okai is vannak a determinisztikus erőforrás-kezelés nyelvi támogatásának fejlődésével kapcsolatban.

✓ A DO implementálja az egyszerű ártalmatlanítási mintát az eldobható típusok példányait tartalmazó típusok esetében. Az alapmintával kapcsolatos részletekért tekintse meg az Alapszintű elidegenítési minta szakaszt.

Ha egy típus felelős más eldobható objektumok élettartamáért, a fejlesztőknek is rendelkezniük kell velük. A tároló metódusának Dispose használatával ezt kényelmesen teheti meg.

✓ A DO implementálja az alapszintű ártalmatlanítási mintát, és véglegesítőt biztosít az olyan erőforrásokat tartalmazó típusok számára, amelyeket explicit módon kell felszabadítani, és amelyek nem rendelkeznek véglegesítőkkel.

A mintát például nem felügyelt memóriapuffereket tároló típusok esetében kell implementálnia. A Véglegesíthető típusok szakasz a véglegesítők implementálásával kapcsolatos irányelveket ismerteti.

✓ Fontolja meg az alapszintű elidegenítési minta implementálását olyan osztályokon, amelyek maguk nem rendelkeznek nem felügyelt erőforrásokkal vagy eldobható objektumokkal, de valószínűleg rendelkeznek olyan altípusokkal, amelyek igen.

Erre jó példa az System.IO.Stream osztály. Bár ez egy absztrakt alaposztály, amely nem rendelkezik erőforrásokkal, a legtöbb alosztálya igen, és emiatt implementálja ezt a mintát.

Alapszintű elidegenítési minta

A minta alapvető implementációja magában foglalja az System.IDisposable interfész implementálását és a metódus és az Dispose(bool) opcionális véglegesítő között Dispose megosztható összes erőforrás-tisztítási logikát implementáló metódus deklarálását.

Az alábbi példa az alapminta egyszerű implementálását mutatja be:

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

A logikai paraméter disposing azt jelzi, hogy a metódust a megvalósításból vagy a IDisposable.Dispose véglegesítőből hívták-e meg. Az Dispose(bool) implementációnak ellenőriznie kell a paramétert, mielőtt más referenciaobjektumokhoz (például az előző mintában szereplő erőforrásmezőhöz) fér hozzá. Ezeket az objektumokat csak akkor szabad elérni, ha a metódust a IDisposable.Dispose végrehajtásból hívják meg (ha a disposing paraméter értéke igaz). Ha a metódus meghívása a véglegesítőből történik (disposing hamis), más objektumok nem érhetők el. Ennek az az oka, hogy az objektumok kiszámíthatatlan sorrendben lesznek véglegesítve, így ezek vagy függőségeik már véglegesítve lehetnek.

Ez a szakasz olyan alapokkal rendelkező osztályokra is vonatkozik, amelyek még nem implementálják az Elidegenítési mintát. Ha olyan osztálytól örököl, amely már implementálja a mintát, egyszerűen bírálja felül a metódust, Dispose(bool) hogy további erőforrás-tisztítási logikát biztosítson.

✓ DO deklarál egy metódust protected virtual void Dispose(bool disposing) , amely központosítja a nem felügyelt erőforrások kiadásával kapcsolatos összes logikát.

Ebben a metódusban minden erőforrás-törlésnek meg kell történnie. A metódust a finalizer és a metódus is meghívja IDisposable.Dispose . A paraméter hamis lesz, ha egy véglegesítőn belülről hívják meg. Ezzel biztosítható, hogy a véglegesítés során futó kódok ne férhessenek hozzá más véglegesíthető objektumokhoz. A véglegesítők implementálásának részleteit a következő szakaszban ismertetjük.

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

✓ DO implementálja a IDisposable felületet egyszerűen hívja Dispose(true) meg, majd .GC.SuppressFinalize(this)

A hívásnak SuppressFinalize csak akkor kell történnie, ha Dispose(true) sikeresen végrehajtja.

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

X NE tegye virtuálissá a paraméter nélküli metódust Dispose .

A Dispose(bool) metódust az alosztályok felül kell bírálni.

// 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 NE deklarálja a metódus túlterhelését, kivéve Dispose() és DisposeDispose(bool).

Dispose fenntartott szónak kell tekinteni, amely segít kodifikálni ezt a mintát, és megelőzni a megvalósítók, a felhasználók és a fordítók közötti félreértéseket. Egyes nyelvek dönthetnek úgy, hogy bizonyos típusokon automatikusan implementálják ezt a mintát.

✓ A DO lehetővé teszi a Dispose(bool) metódus többszöri meghívását. A metódus dönthet úgy, hogy nem tesz semmit az első hívás után.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

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

X KERÜLJE a kivételt belülről Dispose(bool) , kivéve azokat a kritikus helyzeteket, amikor a tárolófolyamat sérült (szivárgások, inkonzisztens megosztott állapot stb.).

A felhasználók azt várják, hogy a hívás Dispose nem fog kivételt képezni.

Ha Dispose kivételt okozhatna, a további blokkok törlési logikája nem lesz végrehajtva. Ennek megkerüléséhez a felhasználónak be kell csomagolnia minden hívást Dispose (a végső blokkon belül!) egy próbablokkba, ami nagyon összetett törléskezelőkhöz vezet. Metódus végrehajtása Dispose(bool disposing) esetén soha ne adjon kivételt, ha a közzététel hamis. Ez a művelet megszakítja a folyamatot, ha egy véglegesítő környezetben hajtja végre.

✓ DO olyan tagot dob, ObjectDisposedException amely az objektum megsemmisítése után nem használható.

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

✓ FONTOLJA meg a metódus Close()megadását Dispose()a területen a standard terminológia mellett az if close kifejezés mellett.

Ebben az esetben fontos, hogy az implementáció azonos Dispose legyen a Close módszerrel, és megfontolja a IDisposable.Dispose módszer explicit implementálását.

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

Véglegesíthető típusok

A véglegesíthető típusok olyan típusok, amelyek kibővítik az alapszintű elidegenítési mintát a véglegesítő felülírásával és a véglegesítési kód elérési útjának megadásával a Dispose(bool) metódusban.

A véglegesítők közismerten nehezen implementálhatók helyesen, elsősorban azért, mert a végrehajtás során nem lehet bizonyos (általában érvényes) feltételezéseket tenni a rendszer állapotáról. Az alábbi irányelveket körültekintően kell figyelembe venni.

Vegye figyelembe, hogy az irányelvek némelyike nem csak a metódusra vonatkozik, hanem a Finalize véglegesítőtől hívott kódokra is. A korábban definiált alapszintű elidegenítési minta esetében ez azt a logikát jelenti, amely akkor fut, Dispose(bool disposing) ha a disposing paraméter hamis.

Ha az alaposztály már véglegesíthető, és implementálja az alapszintű elidegenítési mintát, ne bírálja felül Finalize újra. Ehelyett csak felül kell bírálnia a metódust, Dispose(bool) hogy további erőforrás-tisztítási logikát biztosítson.

Az alábbi kód egy véglegesíthető típus példáját mutatja be:

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 AVOID , hogy a típusok véglegesíthetők legyenek.

Alaposan gondolja át azokat az eseteket, amikor úgy gondolja, hogy döntősre van szükség. A véglegesítőkkel rendelkező példányok valós költségekkel járnak, mind teljesítmény, mind kódbonyolítás szempontjából. Előnyben részesítse az erőforrásburkolókat, például SafeHandle a nem felügyelt erőforrások beágyazása, ahol lehetséges, ez esetben a véglegesítő szükségtelenné válik, mert a burkoló felelős a saját erőforrás-tisztításáért.

X NE tegye véglegesíthetővé az értéktípusokat.

A CLR csak a referenciatípusokat véglegesíti, így a rendszer figyelmen kívül hagyja a véglegesítő értéktípusra tett minden kísérletet. A C# és a C++ fordítók kényszerítik ezt a szabályt.

✓ A DO véglegesíthetővé tesz egy típust, ha a típus felelős egy nem felügyelt erőforrás felszabadításáért, amely nem rendelkezik saját véglegesítővel.

A véglegesítő megvalósításakor egyszerűen hívja meg Dispose(false) és helyezze el az összes erőforrás-törlési logikát a Dispose(bool disposing) metóduson belül.

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

✓ DO implementálja az alapszintű ártalmatlanítási mintát minden véglegesíthető típuson.

Ez lehetővé teszi az ilyen típusú felhasználók számára, hogy explicit módon végezzenek determinisztikus tisztítást ugyanazon erőforrásokról, amelyekért a véglegesítő felelős.

X NE érje el a véglegesíthető objektumokat a véglegesítő kód elérési útján, mert jelentős a veszélye annak, hogy már véglegesítve lesznek.

Például egy véglegesíthető A objektum, amely egy másik véglegesíthető objektumra hivatkozik, B nem használható megbízhatóan az A véglegesítőjében, vagy fordítva. A döntősöket véletlenszerű sorrendben hívjuk meg (a kritikus véglegesítés gyenge rendezési garanciája miatt).

Vegye figyelembe azt is, hogy a statikus változókban tárolt objektumok összegyűjtése bizonyos pontokon történik az alkalmazástartomány kiürítése vagy a folyamatból való kilépés során. Egy véglegesíthető objektumra hivatkozó statikus változó elérése (vagy statikus változókban tárolt értékeket használó statikus metódus meghívása) nem biztos, ha Environment.HasShutdownStarted igaz értéket ad vissza.

✓ TEGYE védetté a metódust Finalize .

A C#, a C++ és a VB.NET fejlesztőknek nem kell emiatt aggódniuk, mert a fordítók segítenek érvényesíteni ezt az útmutatót.

X NE hagyja, hogy a kivételek kikerüljenek a véglegesítő logikából, kivéve a rendszerkritikus hibákat.

Ha kivételt kap egy véglegesítőtől, a CLR leállítja a teljes folyamatot (.NET-keretrendszer 2.0-s verziótól kezdve), megakadályozva, hogy más véglegesítők ellenőrzött módon hajtsa végre és felszabadítsa az erőforrásokat.

✓ Fontolja meg egy kritikus fontosságú véglegesíthető objektum (egy típushierarchiával CriticalFinalizerObjectrendelkező típus) létrehozását és használatát olyan helyzetekben, amikor a véglegesítőnek feltétlenül végre kell hajtania még a kényszerített alkalmazástartományok kiürítése és a szálak megszakítása esetén is.

© Részletek 2005, 2009 Microsoft Corporation. Minden jog fenntartva.

Reprinted by permission of Pearson Education, Inc. from Framework Design Guidelines: Conventions, Idioms and Patterns for Reusable .NET Libraries, 2nd Edition by Krzysztof Cwalina and Brad Abrams, published 22, 2008 by Addison-Wesley Professional, a Microsoft Windows Development Series részeként.

Lásd még