Friday 12 May 2006

Zwalnianie zasobów (C#)

Jeśli dopiero co zaczynasz korzystać z platformy .Net, a wcześniej nie pisałeś w językach z automatycznym zarządzaniem pamięcią, to na pewno wcześniej czy później popełnisz ten sam błąd co ja. Wyobraź sobie, że pisana przez Ciebie klasa wykorzystuje jakieś zasoby systemowe niezarządzane przez Garbage Collector,. Jak je zwolnić? Naturalne wydaje się zrobienie tego w destruktorze, prawda? Przecież w C# ma on nawet tą samą znaną składnię, ~DummyClass(), co z tego, że nazywa się finalizer?


Haczyki są trzy. Pierwszy polega na tym, że finalizer zostanie wywołany w momencie sprzątania obiektu przez Garbage Collector, a to może stać się długo po usunięciu ostatniego do niego odwołania. Drugi - finalizer nie zostanie wywołany w ogóle, jeśli cała aplikacja jest właśnie wyłączana. Trzeci - obiekty posiadające finalizer śledzone odrębnie od nie posiadających go i usuwane z pamięci później. Całe to zachowanie wynika z faktu, że w środowisku z zarządzaną pamięcią klasyczna implementacja destruktora jest bardzo niewydajna, jeśli nie w ogóle niemożliwa.


Jak więc zwolnić takie zasoby? Należy zaimplementować interfejs IDisposable i pamiętać, by go używać, korzystając z implementujących go klas. Służy do tego specjalne słowo kluczowe using, gwarantujące, że na "opakowanym" w nie obiekcie zostanie wywołane Dispose(), niezależnie od tego,czy kod w klauzuli zakończy się normalnie czy też wyjątkiem. Używa się tego konstruktu tak:


using(DummyObject something = new DummyObject())
{
//jakiś kod, korzystający z obiektu something
}

Gdy korzystamy z obiektów implementujących IDisposable, a niemożliwe jest ujęcie ich w ramy using, należy je zwalniać w metodzie Dispose() korzystającego z nich obiektu.


I jeszcze przykładowe zastosowanie IDisposable, bo w nim też łatwo narobić błędów:


/// <summary></summary>
public class DisposableObject : IDisposable
{
#region implementacja IDisposable
/// <summary>
/// Finalizer, wywołuje Dispose,
/// <strong>nie nalezy na nim polegać!</strong>.
/// </summary>
~DisposableObject()
{
Dispose(false);
}

/// <summary>
/// Zwalnia używane zasoby. Wyrejestrowuje finalizer,
/// by niepotrzebnie nie opóźniać zwalniania pamięci.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Zwalnia używane zasoby.
/// </summary>
/// <param name="disposing">czy mamy czas na dokładne sprzątanie?</param>
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
//tutaj trafia to, co _powinniśmy_ posprzątać
}
//a tutaj to, co _musimy_ posprzątać
}
disposed = true;
}

/// <summary>Aby tylko raz zwalniac zasoby.
/// (Dispose może zostać wywołane wielokrotnie.)</summary>
protected bool disposed = false;
#endregion
}

Jeśli nasza klasa dziedziczy z innej, już implementującej IDisposable, to wystarczy ograniczyć się do pokrycia metody protected virtual void Dispose(bool disposing), pamiętając, by wywołać w niej base:Dispose(disposing).


Na koniec ważna uwaga odnośnie kooperacji C++ i .Net. W zarządzanym C++ za pomocą składni ~DummyObject() definiuje się metodę Dispose()! Analogicznie, delete someObject oznacza wywołanie na nim metody Dispose(). Finalizer można zaimplementować poprzez !DummyObject().

No comments:

Post a Comment