Rozšířený vs. zjednodušený Disposable pattern

Tomáš Holan       10.04.2013       C#, Architektura       12273 zobrazení

Podle mnoha zdrojů (například zde a zde) by správná implementace disposable patternu (dále budu tento způsob označovat jako tzv. rozšířený disposable pattern) v jazyce C# tj. korektní implementace interface IDisposable měla pro base třídu vypadat takto:

public class DisposableObject : IDisposable
{
    #region member varible and default property initialization
    private bool IsDisposed;
    #endregion

    #region constructors and destructors
    ~DisposableObject()
    {
        Dispose(false);
    }
    #endregion

    #region action methods
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion

    #region private member functions
    protected virtual void Dispose(bool disposing)
    {
        if (!this.IsDisposed)
        {
            if (disposing)
            {
                //Clean up all managed resources
            }
            //Clean up all native resources
            this.IsDisposed = true;
        }
    }
    #endregion
}

A pro derived třídu takto:

public sealed class DerivedDisposableObject : DisposableObject
{
    #region member varible and default property initialization
    private bool IsDisposed;
    #endregion

    #region private member functions
    protected override void Dispose(bool disposing)
    {
        try
        {
            if (!this.IsDisposed)
            {
                if (disposing)
                {
                    //Clean up all managed resources
                }
                //Clean up all native resources
                this.IsDisposed = true;
            }
        }
        finally
        {
            base.Dispose(disposing);
        }
    }
    #endregion
}

Pokud by třída DisposableObject byla sealed bude se implementace lišit pouze tím, že metoda Dispose(bool) bude deklarována jako private namísto protected virtual:

public sealed class DisposableObject : IDisposable
{
    //...

    #region private member functions
    private void Dispose(bool disposing)
    {
        //...
    }
    #endregion
}

Takto byl disposable pattern původně navržen už pro první verze .NETu. Přestože tento způsob je obecný tedy i obecně (mechanicky) aplikovatelný, postupem času se ukázalo, že má jeden problém. Tím problémem je to, že takto implementovaný objekt obsahuje vždy finalizer.

Finalizer by se správně měl ve třídě zavést pouze pokud ho opravdu potřebujeme tj. pokud třída používá unmanaged zdroje. Takových objektů je v praxi ale opravdu velmi velmi málo, zatímco objektů implementující deterministický úklid pomoci metody Dispose je naopak poměrně velké množství. Hlavním důvodem výše uvedeného je vyšší výkonová náročnost pro úklid objektu vyžadující finalizaci (při vytvoření a následného úklidu většího počtu instancí je obvykle vysoká zátěž na GC patrná). Dalšími jsou pak zbytečná složitost rozšířeného disposable patternu nebo například to, že některé týmy do svých code rules zahrnují i pravidlo, které implementaci finalizeru úplně zakazuje. 

Proto je v praxi doporučeno použit rozšířený disposable pattern pouze pro objekty, které vyžadují finalizaci.

Obecně také platí, že korektní implementace finalizeru není vůbec jednoduchá záležitost. Pokud tedy implementujeme objekt používající unmanaged zdroje,  je třeba dále vzít v úvahu následující:

  • Pokud ne všechny třídy v hierarchii dědění finalizer potřebují, je možné použít tuto implementaci patternu s tím rozdílem, že finalizer (volající Dispose(false)) do implementace zahrneme jen u tříd vyžadující finalizaci. (Například tedy pokud base třída finalizer nepotřebuje, ale předpokládáme, že některá z derived tříd ho vyžadovat bude, tento pattern použijeme, ale finalizer do base třídy neuvedeme.)
  • Pozor, že finalizer je volán z vlákna GC, takže metoda Dispose(bool) musí být minimálně pro větev disposing = false vždy napsaná jako tread safe.
  • Finalizer by také nikdy neměl vyhazovat výjimku, větev disposing = false metody Dispose(bool) by tedy měla být ošetřena například pomoci try { /*…*/ } catch { }.
  • Přístup k veškerým vnitřním instancím musí být ve větvi disposing = false metody Dispose(bool) ošetřen pomoci if (obj != null), protože tyto objekty mohou být uvolněny dříve než vlastní “this” objekt.

Pro objekty, které nepoužívající unmanaged zdroje (tedy u většiny disposable objektů), je v praxi lepší použit pouze zjednodušenou implementaci disposable patternu (tzv. zjednodušený disposable pattern).

Ten pro base třídu vypadá takto:

public class DisposableObject : IDisposable
{
    #region member varible and default property initialization
    private bool IsDisposed;
    #endregion

    #region action methods
    public virtual void Dispose()
    {
        if (!this.IsDisposed)
        {
            //Clean up
            this.IsDisposed = true;
        }
    }
    #endregion
}

A pro derived třídu takto:

public class DerivedDisposableObject : DisposableObject
{
    #region member varible and default property initialization
    private bool IsDisposed;
    #endregion

    #region private member functions
    public override void Dispose()
    {
        try
        {
            if (!this.IsDisposed)
            {
                //Clean up 
                this.IsDisposed = true;
            }
        }
        finally
        {
            base.Dispose();
        }
    }
    #endregion
}

U sealed třídy se implementace zjednodušeného disposable patternu bude lišit pouze v tom, že metoda Dispose nebude virtual:

public sealed class DisposableObject : IDisposable
{
    #region member varible and default property initialization
    private bool IsDisposed;
    #endregion

    #region private member functions
    public void Dispose()
    {
        if (!this.IsDisposed)
        {
            //Clean up
            this.IsDisposed = true;
        }
    }
    #endregion
}

Další články zabývající se tímto tématem lze nalézt například zde, zde a zde.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback