Vzor pro metodu Dispose

Poznámka:

Tento obsah je znovu vytištěn oprávněním Pearson Education, Inc. z Framework Design Guidelines: Conventions, Idioms a Patterns for Reusable .NET Libraries, 2. vydání. Tato edice byla publikována v roce 2008 a kniha byla od té doby plně upravena ve třetím vydání. Některé informace na této stránce můžou být zastaralé.

Všechny programy získávají během provádění jeden nebo více systémových prostředků, jako je paměť, systémová zpracování nebo připojení k databázi. Vývojáři musí být opatrní při používání takových systémových prostředků, protože je nutné je po získání a použití uvolnit.

CLR poskytuje podporu pro automatickou správu paměti. Spravovaná paměť (přidělená paměť pomocí operátoru newjazyka C#) nemusí být explicitně vydána. Uvolní se automaticky uvolňováním paměti (GC). Tím se vývojářům uvolní zdlouhavá a obtížná úloha uvolnění paměti a je jedním z hlavních důvodů pro nevídanou produktivitu poskytovanou rozhraním .NET Framework.

Spravovaná paměť je bohužel jedním z mnoha typů systémových prostředků. Prostředky jiné než spravovaná paměť je potřeba explicitně vydat a označují se jako nespravované prostředky. GC nebyl speciálně navržen tak, aby spravoval takové nespravované prostředky, což znamená, že odpovědnost za správu nespravovaných prostředků leží v rukou vývojářů.

CLR poskytuje určitou pomoc při vydávání nespravovaných prostředků. System.Object deklaruje virtuální metodu Finalize (označovanou také jako finalizátor), která je volána uvolňováním paměti objektu před uvolněním paměti objektu uvolňováním paměti a lze ji přepsat k uvolnění nespravovaných prostředků. Typy, které přepisují finalizační metodu, se označují jako finalizovatelné typy.

I když finalizátory jsou v některých scénářích čištění efektivní, mají dvě významné nevýhody:

  • Finalizátor je volána, když uvolňování paměti zjistí, že objekt je způsobilý pro kolekci. K tomu dochází v určitém nedeterminovaném časovém období po tom, co už prostředek není potřeba. Zpoždění mezi tím, kdy by vývojář mohl nebo chtěl uvolnit prostředek, a čas, kdy je prostředek skutečně uvolněn finalizátorem, může být nepřijatelné v programech, které získávají mnoho málo prostředků (prostředky, které se dají snadno vyčerpat) nebo v případech, kdy jsou prostředky nákladné pro zachování používání (např. velké nespravované vyrovnávací paměti).

  • Když CLR potřebuje volat finalizátor, musí odložit shromažďování paměti objektu do dalšího kola uvolňování paměti (finalizátory se spouští mezi kolekcemi). To znamená, že paměť objektu (a všechny objekty, na které odkazuje), se po delší dobu nevyvolá.

Proto spoléhat se výhradně na finalizátory nemusí být vhodné v mnoha scénářích, kdy je důležité co nejrychleji uvolnit nespravované prostředky, při řešení nedostatku prostředků nebo ve vysoce výkonných scénářích, ve kterých je dodatečná režie uvolňování paměti při finalizaci nepřijatelná.

Architektura poskytuje System.IDisposable rozhraní, které by mělo být implementováno, aby vývojáři poskytli ruční způsob, jak uvolnit nespravované prostředky, jakmile nejsou potřeba. Poskytuje také metodu GC.SuppressFinalize , která může informovat GC, že objekt byl ručně odstraněn a už není nutné dokončit, v takovém případě lze paměť objektu uvolnit dříve. Typy, které implementují IDisposable rozhraní, se označují jako jednorázové typy.

Model Dispose je určen ke standardizaci použití a implementace finalizátorů a IDisposable rozhraní.

Hlavní motivací modelu je snížit složitost implementace Finalize metod a Dispose metod. Složitost vychází ze skutečnosti, že metody sdílejí některé, ale ne všechny cesty kódu (rozdíly jsou popsány dále v kapitole). Kromě toho existují historické důvody pro některé prvky vzoru související s vývojem podpory jazyka pro deterministické řízení prostředků.

• Implementujte základní model Dispose u typů obsahujících instance uvolnitelných typů. Podrobnosti o základním vzoru najdete v části Základní vzorek Dispose .

Pokud je typ zodpovědný za životnost jiných uvolnitelných objektů, vývojáři potřebují také způsob, jak je odstranit. Použití metody kontejneru Dispose je pohodlný způsob, jak to udělat.

• Implementujte základní model Dispose a poskytněte finalizátor typů obsahujících prostředky, které musí být explicitně uvolněny a které nemají finalizátory.

Model by měl být například implementován u typů, které ukládají nespravované vyrovnávací paměti. Část Finalizovatelné typy popisuje pokyny týkající se implementace finalizátorů.

• ZVAŽTE implementaci základního vzoru Dispose u tříd, které samy neobsahují nespravované prostředky nebo uvolnitelné objekty, ale pravděpodobně mají podtypy, které dělají.

Skvělým příkladem této třídy je System.IO.Stream . I když je to abstraktní základní třída, která neobsahuje žádné prostředky, většina jejích podtříd dělá a z tohoto důvodu implementuje tento vzor.

Základní vzor Dispose

Základní implementace modelu zahrnuje implementaci System.IDisposable rozhraní a deklarování Dispose(bool) metody, která implementuje veškerou logiku čištění prostředků, která se má sdílet mezi metodou Dispose a volitelným finalizátorem.

Následující příklad ukazuje jednoduchou implementaci základního vzoru:

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

Logický parametr disposing označuje, zda byla metoda vyvolána z IDisposable.Dispose implementace nebo finalizátoru. Implementace Dispose(bool) by měla před přístupem k jiným referenčním objektům zkontrolovat parametr (např. pole zdroje v předchozí ukázce). Tyto objekty by měly být přístupné pouze v případě, že metoda je volána z IDisposable.Dispose implementace (pokud disposing je parametr roven true). Pokud je metoda vyvolána z finalizátoru (disposing je false), jiné objekty by neměly být přístupné. Důvodem je, že objekty jsou finalizovány nepředvídatelným pořadím, takže již mohly být dokončeny nebo některé z jejich závislostí.

Tato část se vztahuje také na třídy se základem, který ještě neimplementuje Dispose Pattern. Pokud dědíte z třídy, která už model implementuje, jednoduše přepište metodu Dispose(bool) , aby poskytovala další logiku vyčištění prostředků.

• Deklarujte metodu, která centralizuje veškerou protected virtual void Dispose(bool disposing) logiku související s uvolněním nespravovaných prostředků.

V této metodě by mělo dojít k vyčištění všech prostředků. Metoda se volá z finalizátoru IDisposable.Dispose i metody. Parametr bude false, pokud je vyvolán z uvnitř finalizátoru. Měl by se použít k zajištění, že veškerý kód spuštěný během finalizace nemá přístup k jiným finalizovatelným objektům. Podrobnosti o implementaci finalizátorů jsou popsány v další části.

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

• ImplementujteIDisposable rozhraní jednoduše voláním Dispose(true) následovaným GC.SuppressFinalize(this).

Volání SuppressFinalize by mělo proběhnout pouze v případě, že Dispose(true) se úspěšně provede.

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

X NEstavte metodu bez Dispose parametrů jako virtuální.

Metoda Dispose(bool) je metoda, která by měla být přepsána podtřídami.

// 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 DO NOT deklaruje žádná přetížení Dispose jiné metody než Dispose() a Dispose(bool).

Dispose by mělo být považováno za vyhrazené slovo, které pomůže tento vzor codififikovat a zabránit nejasnostem mezi implementátory, uživateli a kompilátory. Některé jazyky se můžou rozhodnout tento model automaticky implementovat u určitých typů.

• UmožňujeDispose(bool), aby byla metoda volána více než jednou. Metoda se může rozhodnout, že po prvním volání nic neudělá.

public class DisposableResourceHolder : IDisposable {

    bool disposed = false;

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

X AVOID vyvolá výjimku z rozsahu Dispose(bool) s výjimkou kritických situací, kdy byl proces obsahující poškozen (úniky, nekonzistentní sdílený stav atd.).

Uživatelé očekávají, že volání Dispose nevyvolá výjimku.

Pokud Dispose by mohla vyvolat výjimku, logika dalšího vyčištění bloku se nespustí. Aby to uživatel mohl obejít, bude muset zabalit každé volání Dispose do bloku try (v rámci posledního bloku!), což vede k velmi složitým obslužným rutinům čištění. Pokud provádíte metodu, nikdy nevyvolejte Dispose(bool disposing) výjimku, pokud je odstranění nepravdivé. Tímto způsobem proces ukončíte, pokud se provádí uvnitř kontextu finalizátoru.

• Vyvolejte od libovolného ObjectDisposedException člena, který nelze použít po odstranění objektu.

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

• ZVAŽTE poskytnutí metody Close(), kromě Dispose(), pokud je uzavření standardní terminologií v oblasti.

Při tom je důležité, abyste implementaci udělali Close identickou Dispose a zvažte explicitní implementaci IDisposable.Dispose metody.

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

Finalizovatelné typy

Finalizovatelné typy jsou typy, které rozšiřují Základní Dispose Vzor přepsáním finalizátoru a poskytnutím cesty kódu finalizace v Dispose(bool) metodě.

Finalizátory se obtížně implementují správně, a to především proto, že během provádění nelze provést určité (obvykle platné) předpoklady o stavu systému. Pečlivě je třeba vzít v úvahu následující pokyny.

Všimněte si, že některé pokyny se vztahují nejen na metodu Finalize , ale na jakýkoli kód volaný z finalizátoru. V případě dříve definovaného vzoru Basic Dispose to znamená logiku, která se spustí uvnitř Dispose(bool disposing) , když disposing je parametr false.

Pokud je základní třída již finalizovatelná a implementuje základní dispose vzor, neměli Finalize byste přepisovat znovu. Místo toho byste měli metodu Dispose(bool) přepsat tak, aby poskytovala další logiku vyčištění prostředků.

Následující kód ukazuje příklad finalizovatelného typu:

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 VYHNĚTE se finalizovat typům.

Pečlivě zvažte jakýkoli případ, ve kterém si myslíte, že finalizátor je potřeba. Z hlediska výkonu i složitosti kódu existují skutečné náklady spojené s instancemi s finalizátory. Pokud je to možné, použijte obálky prostředků, jako SafeHandle je zapouzdření nespravovaných prostředků, v takovém případě se finalizátor stane zbytečným, protože obálka zodpovídá za vyčištění vlastního prostředku.

X NEdělat typy hodnot finalizovatelné.

Modul CLR ve skutečnosti finalizuje pouze odkazové typy, a proto všechny pokusy o umístění finalizátoru na typ hodnoty budou ignorovány. Kompilátory C# a C++ vynucují toto pravidlo.

• Proveďte finalizovat typ, pokud je typ zodpovědný za uvolnění nespravovaného prostředku, který nemá vlastní finalizátor.

Při implementaci finalizátoru jednoduše zavolejte Dispose(false) a umístěte do metody veškerou logiku Dispose(bool disposing) vyčištění prostředků.

public class ComplexResourceHolder : IDisposable {

    ~ComplexResourceHolder() {
        Dispose(false);
    }

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

• Implementujte základní odlikvidovací vzor na každém finalizovatelném typu.

To uživatelům typu dává prostředek k explicitně deterministickému vyčištění stejných prostředků, za které je finalizátor zodpovědný.

X NEMÁ přístup k žádným finalizovatelným objektům v cestě kódu finalizátoru, protože existuje značné riziko, že již budou dokončeny.

Například finalizovatelný objekt A, který má odkaz na jiný finalizovatelný objekt B nemůže spolehlivě použít B v finalizátoru A nebo naopak. Finalizační metody jsou volány v náhodném pořadí (zkratka slabé záruky řazení pro kritickou finalizaci).

Mějte také na paměti, že objekty uložené ve statických proměnných se během uvolnění domény aplikace nebo při ukončení procesu shromažďují v určitých bodech. Přístup ke statické proměnné, která odkazuje na finalizovatelný objekt (nebo volání statické metody, která může používat hodnoty uložené ve statických proměnných), nemusí být bezpečné, pokud Environment.HasShutdownStarted vrátí hodnotu true.

- Udělejte svou Finalize metodu chráněnou.

Vývojáři C#, C++ a VB.NET se o to nemusí starat, protože kompilátory pomáhají vynucovat toto vodítko.

X NENECHÁ VÝJIMKY utéct z logiky finalizátoru, s výjimkou systémově kritických selhání.

Pokud dojde k výjimce z finalizátoru, modul CLR vypne celý proces (od rozhraní .NET Framework verze 2.0), zabrání ostatním finalizátorům v provádění a uvolnění prostředků řízeným způsobem.

• ZVAŽTE vytvoření a použití kritického finalizovatelného objektu (typ s hierarchií typů, která obsahuje CriticalFinalizerObject) v situacích, kdy finalizátor musí být proveden i v případě uvolnění domény vynucené aplikace a přerušení vláken.

© Části 2005, 2009 Microsoft Corporation. Všechna práva vyhrazena.

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 Oct 22, 2008 by Addison-Wesley Professional v rámci Microsoft Windows Development Series.

Viz také