Dispose and Destructors in C#

Oct 08 2015

Sorry, this entry is only available in Czech. For the sake of viewer convenience, the content is shown below in the alternative language. You may click the link to switch the active language.

Jak správně na Implementaci IDisposable objektů aneb jak funguje Garbage Collection a destruktory v C#.

Garbage Collection

Krátká odbočka ke správě paměti v C# (uvolňování nepoužívaných objektů). Stará se nám o ni automaticky GC, takže se o ni nemusíme starat sami, tj. nemusíme explicitně uvolňovat objekty. GC je samostatné vlákno (vlastně několik vláken), které:

  • hledá objekty, které už nejsou používány
  • likviduje tyto objekty
  • uvolňuje zdroje držené těmito objekty

Destruktory a finalizace objektů

Destruktor je část kódu, která se provede ve chvíli, kdy je objekt zlikvidován. Destruktor v C# je v podstatě přepsání(override) metody Finalize(). Ve chvíli, kdy runtime určí, že likvidovaný objekt podporuje finalizaci, tak neni zlikvidován, ale je přesunut do freachable (finalization reachable table). Zároveň vznikne nové vlákno, které až při příštím úklidu zavolá metodu Finalize() a objekt uvolní. Takže likvidace objektu s destruktorem je podstatně náročnější, než likvidace normálního objektu. A to není jediný problém s destruktory v C#.

IDisposable

GC je výhoda, za kterou platíme několika problémy na které je třeba dávat pozor. Příčinou těchto problémů je, že GC objekty likviduje po tom co přestanou být používány, ale za tak dlouho jak se mu zachce. Takže než se GC rozhoupe, tak objekt stále drží veškeré zdroje, které si za svého života uzmul. Zdroje jsou paměť, otevřené soubory, přípojení k DB a další. Aby bylo možné explicitně uvolnit zdroje jako databázová připojení, je v .NET zavedené rozhraní IDisposable, které předepisuje metodu Dispose(). Vrámci této metody by měl objekt uvolnit veškeré zdroje a zavolat Dispose() na podřízených objektech. Každý programátor pak musí volat metodu Dispose() pokud ji objekt podporuje, případně použít konstrukci using.

using(IDisposable)
{
  //použití IDisposable objektu, 
  //na konci bloku je objekt uvolněn (je zavoláno Dispose())
}

 

Správná implementace

Pokud by všichni poctivě volali metodu Dispose() na všech IDisposable objektech, tak by stačilo u objektů kde je to třeba tuto metodu správně implementovat. Ale ono se to tak poctivě většinou nedělá. Navíc když je objekt odstraněn GC, tak automaticky metodu Dispose() nezavolá. Takže je dobré pro IDisposable objekty implementovat destruktor, ve kterém se zavolá metoda Dispose(). Ještě je třeba rozlišit přímé volání Dispose() od volání z destruktoru, protože když je ničen objekt, tak už mohou být zničené jeho podobjekty. Takže typická implementace by mohla být takto:

public class MyClass : IDisposable
{
  private bool disposed;

  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
  public void Dispose(bool disposing)
  {
    if (!this.disposed)
    {
      if (disposing)
      {
        //zlikviduju řízené objekty
      }
      //zlikviduju neřízené objekty
      this.disposed = true; 
    }
  }

  ~MyClass()
  {
    Dispose(false);
  }
}

Ještě stojí za zmínku řádek GC.SuppressFinalize(this). Toto volání řekne GC, že už není třeba objekt složitě likvidovat a volat Finalize(), protože už uvolnil všechny potřebné zdroje. Tím se uvolnění objektu zjednoduší.

Zatím žádná reakce

Vítám tvoje reakce