Managed vs. Unmanaged prostředky
Prostředky v prostředí .NET se dělí na spravované (managed) a nespravované (unmanaged). Spravované prostředky jsou ty, které plně spravuje běhové prostředí CLR (Common Language Runtime), jako jsou objekty uložené na haldě, jejichž životní cyklus hlídá Garbage Collector.
Naproti tomu nespravované prostředky představují entity, o které se CLR přímo nestará, typicky handle na soubory, síťová spojení, GDI objekty, alokace nativní paměti nebo objekty COM. Pokud se s těmito prostředky nepracuje korektně, může docházet k únikům paměti či k nedostatku systémových prostředků.
Uvolňování paměti
Spravovaná paměť je uvolňována automaticky pomocí Garbage Collectoru. GC ale sleduje pouze objekty na managed haldě a neví nic o nespravovaných prostředcích, které mohou objekty alokovat. Pokud tedy spravovaný objekt drží nespravovaný prostředek (například handle na bitmapu), je nutné takové prostředky manuálně uvolnit dříve, než dojde k destrukci spravovaného objektu. Jinak sice GC objekt jednou uvolní, ale nespravovaný prostředek může zůstat alokovaný, což vede k únikům paměti nebo jiným problémům.
Uvolňování prostředků
Správné uvolňování nespravovaných prostředků se provádí využitím Dispose vzoru skrze implementaci rozhraní IDisposable, které poskytuje metodu Dispose(). Ta slouží k explicitnímu uvolnění všech prostředků, které objekt používá, a to dříve, než by došlo na Garbage Collector a případný finalizér. Implementace Dispose() umožňuje deterministické uvolnění prostředků, tedy přesně v okamžiku, kdy jsou již nepotřebné.
Klíčové slovo using
Pro snazší správu objektů implementujících IDisposable existuje konstrukce using, která zajistí zavolání Dispose() i při výskytu výjimky:
using (var stream = new FileStream("data.txt", FileMode.Open))
{
// Práce se souborem
}
// FileStream.Dispose() zavolán automatickyOd C# 8.0 existuje také using statement bez bloku:
using var stream = new FileStream("data.txt", FileMode.Open);
// Dispose() se zavolá na konci scopeIDisposable
Rozhraní IDisposable obsahuje jedinou metodu - Dispose():
public interface IDisposable
{
void Dispose();
}Objekt, který implementuje IDisposable, signalizuje, že obsahuje prostředky vyžadující ruční uvolnění. Objekty jako FileStream, SqlConnection nebo Bitmap implementují toto rozhraní, protože pracují s nespravovanými prostředky, které mohou být limitované nebo drahé na alokaci.
Ukázka vlastní implementace IDisposable
public class ResourceHolder : IDisposable
{
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Uvolnit managed prostředky
}
// Uvolnit unmanaged prostředky
disposed = true;
}
}
~ResourceHolder()
{
Dispose(false);
}
}Použití GC.SuppressFinalize(this) říká GC, že již není nutné volat finalizátor objektu, pokud byl Dispose zavolán ručně.
Výhody IDisposable a deterministického uvolňování
- Okamžité uvolnění nespravovaných prostředků
- Vyšší kontrola nad životním cyklem objektů
- Zamezení únikům prostředků (například handle, paměť)
Nevýhody
- Nutnost explicitního volání
Dispose()nebo použitíusing - Možnost chyb při implementaci vzoru
Dispose(například chybějícíGC.SuppressFinalize) - Vyšší složitost kódu u komplexních tříd
Speciální případy spravovaných objektů s nespravovanou pamětí
Existují třídy, které jsou na první pohled spravované objekty, ale uvnitř alokují velké množství nespravované paměti. Typickým příkladem je třída Bitmap. Objekt Bitmap je běžný spravovaný objekt, ale zároveň alokuje velké bloky nespravované paměti pro uložení obrazových dat. Pro GC tak Bitmap působí jako relativně malý objekt (z pohledu velikosti referencí), ale ve skutečnosti spotřebovává výrazně více paměti.
.NET Framework proto obsahuje mechanismus, který těmto objektům umožňuje říct GC, že jsou „větší, než vypadají“, aby byly sbírány rychleji, pokud je paměťový tlak příliš vysoký. I přesto platí, že u objektů jako Bitmap je silně doporučeno ruční volání Dispose(), protože finalizace těchto objektů může být zbytečně odložena a držet velké množství nespravované paměti.