To będzie pierwszy wpis na temat interfejsu IDisposable w tej krótkiej mini serii z większej serii.
Otóż dobry programista używający jakiegokolwiek obiektu, który implementuje interfejs IDisposable jest zobowiązany do wywołania metody Dispose w momencie gdy skończy pracować z daną instancją klasy. Spowoduje to zwolnienie zasobów przez wykorzystywany obiekt, np. zwrócenie połączenia bazodanowego do puli połączeń, rozłączenie połączenia tcp/ip, zwolnienie jakiegoś niezarządzalnego zasobu czy zwolnienie dużej wewnętrznej struktury.
W bazowym frameworku .Neta niestety możemy spotkać kilka niekonsekwentnych i problematycznych implementacji. Przyjęło się poprzez konwencję, że na niektórych klasach ( np. FileStream ) dostępne są dwie metody: Close i Dispose. Czym one się różnią? Otóż dokładnie niczym.
public virtual void Close() { this.Dispose(true); GC.SuppressFinalize((object) this); } [__DynamicallyInvokable] [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public void Dispose() { this.Close(); }
Zatem czy jest różnica, którą metodę można wołać?
Tak, jest.
Jeśli napiszemy kod, który będzie przypominał poniższy to mamy duży problem. Jaki?
FileStream file = File.Open("processMe.txt", FileMode.Open); // Process file here file.Close();
Otóż w momencie gdy w bloku procesującym poleci wyjątek to metoda Close nie zostanie wywołana i co za tym idzie plik nie zostanie zwolniony. Sam .Net w późniejszym czasie to naprawi (tak, by nie było wycieków niezarządalnych zasobów) poprzez finalizator (o ile jest zaimplementowany). Jednak jest to niedeterministyczne zachowanie na którym polegać nie wolno. Każdemu dobremu programiście nie przytrafi się taka sytuacja, gdyż wie, że należy obiekty implementujące interfejs IDisposable używać wraz z klauzulą using.
using(FileStream f = File.Open("processMe.txt", FileMode.Open)) { // Process file here }
Jeżeli popatrzymy sobie do disassemblera to zobaczymy jak powyższa konstrukcja jest rozwijana.
Dostrzeżemy tam użycie metody Dispose oraz bloku try finally. W momencie gdy poleci wyjątek blok obsługi błędów wykona sprzątanie zasobów i wszystko będzie w porządku.
Powyższy kod otworzony w Reflectorze z wyłączonymi optymalizacjami zostanie zdekompilowany do następującej postaci:
FileStream f; bool CS$4$0000; f = File.Open("processMe.txt", 3); Label_000D: try { goto Label_0021; } finally { Label_0011: if ((f == null) != null) { goto Label_0020; } f.Dispose(); Label_0020:; } Label_0021: return;
Co ciekawe są w .Netcie klasy, które implementują interfejs IDisosable w sposób explicit. Oznacza to, że metoda Dispose nie będzie normalnie widoczna w IntelliSense uniemożliwiając nam szybkie stwierdzenie, czy klasa implementuje wspomniany interfejs i użycie using. W takich przypadkach ja od razu pisz using, a kompilator mi powie, czy jest tam implementacja IDisposable.
Jakie to klasy?