Dispose and Destructors in C#
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ší.