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.