Prawidłowe zwalnianie zasobów

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.

9 thoughts on “Prawidłowe zwalnianie zasobów”

  1. 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);
    }
    }

  2. 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.

  3. 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.

  4. 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 ?

  5. 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 ?

  6. Na przykład dla zasobów unmanaged – zewnętrzne biblioteki lub dużych obiektów, które chcemy sami zwalniać.

  7. 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).

  8. Wydaję mi się, że ten destruktor na wypadek, gdyby użytkownik zapomniał wywołać Dispose/osadzić w using().

Leave a Reply

Your email address will not be published.