W języku c# obiekt, którego zasobami chcemy sami zarządzać, powinien implementować interfejs IDisposable. Sporo osób aby zwolnić zasoby pisze następujący kod:
public class MyClass : IDisposable { #region IDisposable Members public void Dispose() { // zwalnianie zasobow } #endregion }
Interfejs niestety wymusza nam tylko implementację metody Dispose. Powyższe rozwiązanie jest zdecydowanie nieprawidłowe. Zanim jednak przejdę do omawia co w kodzie jest niepoprawnego, podam prawidłową implementację:
public class MyClass:IDisposable { private bool IsDisposed=false; public void Dispose() { Dispose(true); GC.SupressFinalize(this); } protected void Dispose(bool Diposing) { if(!IsDisposed) { if(Disposing) { // zwalniaj zasoby zarządzalne } // zwalniaj zasoby niezarządzalne } IsDisposed=true; } ~MyClass() { Dispose(false); } }
Powyższa implementacja gwarantuje nam, że gdy obiekt będzie automatycznie czyszczony przez GarbageCollector to zasoby niezarządzane i tak zostaną wyczyszczone. Uzyskujemy to dzięki implementacji destruktora, który wywołuje się zawsze gdy GC zwalnia obiekt. Właśnie w przypadku gdy to GC zwalnia obiekt a nie użytkownik ręcznie, metoda Dispose nie jest wywoływana więc jedynym miejscem na zrobienie tego jest destruktor.
Następna kwestia to przypadek gdy to sam użytkownik chce zwolnić obiekt. W tym przypadku musimy poinformować GC, że bierzemy na siebie odpowiedzialność za obiekt(metoda SupressFinalize) i tym samym destruktor nie zostanie wywołany(co uchroni nas przed zapętleniem).
Ponadto dodałem flagę IsDisposed, która umożliwia nam np. wyrzucenie wyjątku w przypadku gdy użytkownik drugi raz chce zwolnić obiekt.
Fajny artykuł, ale ja bym to napisał tak 🙂 :
public class MyClass : IDisposable
{
private bool IsDisposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool Disposing)
{
if (!IsDisposed)
{
if (Disposing)
{
// zwalniaj zasoby zarządzalne
}
// zwalniaj zasoby niezarządzalne
IsDisposed = true;
}
}
~MyClass()
{
Dispose(false);
}
}
Ale to wyjdzie na to samo. Obie wersje są poprawne.
Pozdrawiam
Co do destruktorow to mam mieszane uczucia nie wiem jak obecnie ale byl czas ze potrafily sie nie wywolac. W dodatku wywoluja sie niewiadomo kiedy i niewiadomo z jakiego watku. Mysle ze rozwiazaniem pewniejszym jest tworzenie statycznych referencji do obiektow disposable ktore beda zrywane w Dispose. Jezeli ono nie nastapi w pewnym momencie nasz obiekt zacznie “trzymac” duza iilosc pamieci a jakis profiler wskaze nam czego rzesmy nie disposowali recznie.
Przepraszam że czepię się komentarza Pana MGaszczak’a. Ale nie rozumiem czym Pan chciał zasłynąć wklejając ten sam kod który autor umieścił w swoim poście.
Po za tym w pewnym miejscu kod nie ma sensu:
if (Disposing)
Disposing, biblioteka System.Collections.Generic , nie zawiera takiej zmiennej ani metody.
Nie wiem czy autor się pomylił, czy być może gdzieś miał jakąś metodę zadeklarowaną o tej nazwie. Może się mylę. Dlatego proszę o sprostowanie tego, ponieważ kod dla mnie jako początkującego programisty jest niezrozumiały.
Podsumowując czym jest Disposing, i czy nie powinno tam być po prostu IsDispose ?
if (Disposing) już rozumiem, przepraszam za zaśmiecanie. Jednak mam pytanie w jakim celu jest wykorzystywane zwalnianie pamięci przez siebie skoro zajmuje się tym GC ?
Na przykład dla zasobów unmanaged – zewnętrzne biblioteki lub dużych obiektów, które chcemy sami zwalniać.
Jeżeli blokujemy wywołanie destruktora przez GC.SupressFinalize(this); to czy nie lepiej niedefiniować destruktora i tym samym nie musimy wywoływać GC.SupressFinalize(this);?
Wydaje mi się że wystarczyłaby implementacja Dispose() i Dispose(bool Disposing).
Wydaję mi się, że ten destruktor na wypadek, gdyby użytkownik zapomniał wywołać Dispose/osadzić w using().