Ha objektum-orientált módon fejlesztünk, akkor egy új objektumpéldány létrehozásához egy speciális metódust, a konstuktort fogjuk használni - akár managed, akár unmanaged környezetben tesszük ezt.
Viszont amikor a példányunkra már nincs szükség, unmanaged környezetben egy másik speciális metódus, a destruktor ("megsemmisítő") hívásával takaríthatunk ki magunk után, míg managed környezetben a szemétgyűjtő végzi el helyettünk ezt a feladatot. Kényelmes, nem?
De, viszont lehet vele egy kis bökkenő: egyrészt nem tudjuk, konkrétan mikor is fut le (magyarul: nem-determiniszikus a futás ideje), másrészt, ha nincs destruktor, akkor tejles egészében a GC-re bízzuk az általunk lefoglalt erőforrások felszabadítását, pedig néha hasznos lenne, ha a példányunk megsemmísítésekor mi magunk is eszközölhetnénk némi takarítást. Szerencsére mindkét problémára van megoldás.
C#-ban destruktorunk ugyan nincsen, de van egy speciális, Finalize nevű metódus, ami nagyon hasonló célt szolgál. Annyira hasonló, hogy a deklarálásának a móda azonos a C++-os destruktoréval, azaz ha szeretnék egy finalizer metódust az osztályunkba, akkor a ~OsztályNév() névvel kell ellátnunk, és majd a fordító csinál belőle egy Finalize() metódust.
A finalizer metódust a CLR fogja meghívni, mielőtt a már nem használt objektumpéldányunkat kisöpörné a memóriából. Ez tehát egy remek lehetőség arra, hogy az osztályunkból esetlegesen lefoglalt unmanaged erőforrásokat is felszabadítsuk.
Ezzel az első problémára, miszerint mi nem tudunk beleszólni a példány-megsemmítési folyamatba, meg is van a gyógyír. De mi a helyzet a nem-determinisztikussággal?
Lézezik egy interface, az IDisposable, ami szabad magyar fordításban kb. azt jelenti: "tőlem meg lehet szabadulni". Az IDisposable egyetlen metódus megvalósítását írja elő, a void Dispose()-ét. Ez a metódus ugyanarra szolgál - tehát nekünk azt kell benne implementálni -, amire a finalizer: fel kell benne szabadítani minden olyan erőforrást, amit a GC nem fog tudni. A Dispose, mint minden tisztességes metódus, bármikor meghívható, ezzel tehát a nem-determinisztikusság problémáját is megoldottuk: ha már nem kell az objektumpéldányunk, meghívjuk rajta a Dispose()-t, és kész.
Most mindjárt két megoldásunk is van, és körülbelül ugyanazt kéne megvalósítani a finalizerben, illetve a Dispose-ban. Ez bizony nem túl szép, szerencsére ezt is triviális megoldani: rakjuk ki egy privát metódusba a lényegi kódot, és mindkét helyről hívjunk tovább oda.
Ez már közel tökéletes, akár hátra is dőlhetnénk, de bizony még mindig van egy bibi: ha mi is meghívjuk a Dispose-t, aztán a GC is a finalizert, akkor a felszabadító kód kétszer fog lefutni (már ha tud...). Meg kéne mondani valahogy a GC-nek, hogy ha mi már dispose-oltunk "kézzel", akkor ő hanyagolja a finalizálást. Szerencsére ezt nem túl bonyolult módon közölhetjuk vele, egyszerűen a statikus GC.SuppressFinalize() meghívásával, aminek egy objektum-referenciát kell átadnunk, egész pontosan azét az objektumét, aminek a finalizálását szupresszálni szeretnénk.
Az egész fenti litániát az okosok összefoglalták egy kis kód-darabba, amit a .netes világ úgy hív: Disposable Pattern. Valahogy így néz ki:
class MyType : IDisposable { ~MyType() { Dispose(false); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { // shared cleanup logic if (disposing) { // dispose-specific logic GC.SuppressFinalize(this); } } }Nagyon a magyarázatába nem mennék bele - akinek a fentiek alapján nem tiszta, hogy mit csinál, az álljon be juhásznak. :)

1 megjegyzés:
Ez fain, köszi ;)
Milyen egyszerűnek tűnik, mégis milyen könnyű elfelejteni. Merthogy üzleti igény legtöbbször arra van, hogy megnyomom a nagy piros gombot, akkor történjen ez-meg az, diszpozálást senki nem kér :)
Itt találtam egy kis ötletelést tredszéf diszpozálásról: http://www.csharpfriends.com/Forums/ShowPost.aspx?PostID=60743
Megjegyzés küldése