A .NET beépítetten rendelkezik immutable kollekciókkal, de mik ezek és miért van rájuk szükség? Az eddig bemutatott kollekciók egyike sem immutable. Ez egy szálon futó alkalmazások esetén nem probléma, de több szál esetén, ha nem vagyunk elég óvatosak, szálbiztonsági (thread safety) problémákba ütközhetünk. Ugyan a kollekciók rendelkeznek immutable interfészekkel (pl. IReadOnlyList<T>), de ezek csak olvasást tesznek lehetővé. A legtöbb esetben ez azonban nem elég. Itt jönnek képbe az immutable kollekciók. Ezek lehetővé teszik a biztonságos változtatásokat drága szinkronizációs mechanizmusok nélkül, másolás nélkül vagy minimális másolással.
Egy immutable kollekció olyan objektum, amely minden körülmény között megőrzi a szerkezetét, és nem engedi meg az elem szintű módosításokat, ugyanakkor olyan API-kat kínál, amelyekkel módosításokat lehet végrehajtani. Például ha hozzáadsz egy elemet egy ImmutableStack<T> típusú veremhez, az eredmény két különálló verem lesz, amelyek közül az egyik tartalmazza az új elemet, a másik pedig nem.
Itt érdemes megjegyezni, hogy egy immutable kollekció alkalmazása sem garantálja azt, hogy a maguk az elemek is immutábilisak.
Az immutábilis kollekciók két fő tervezési cél alapján készültek:
- A memória újrafelhasználása – a lehető legkevesebb másolással, a szemétgyűjtő terhelésének minimalizálása mellett.
- Ugyanazoknak a műveleteknek a támogatása, mint amiket a módosítható kollekciók kínálnak – hasonló futásidő-komplexitással.
Elérhető immutable kollekciók:
ImmutableStack<T>ImmutableList<T>ImmutableArray<T>ImmutableDictionary<TKey, TValue>IImmutableSet<T>IImmutableQueue<T>ImmutableHashSet<T>ImmutableSortedSet<T>
Mikor és mikor ne használjuk?
Az immutable kollekciók a korábban említett többszálú környezet mellett funkcionális programozásnál jönnek jól. Még ha egy immutábilis kollekció használata elviekben ideális is, előfordulhat, hogy a teljesítmény nem lesz elfogadható. Ennek az az oka, hogy belső felépítésében ezek a kollekciók eltérnek.
Például az ImmutableStack<T> egy láncolt listaként van implementálva, míg a mutable Stack<T> belsőleg tömbökkel dolgozik. A láncolt listás megvalósítás előnye, hogy az ImmutableStack<T> módosításakor nem kell egy teljes tömböt másolni, elég csak a referenciákat. Ezen elv mentén a komplexebb kollekciók kiegyensúlyozott bináris faként vannak implementálva.
A bináris fa egy olyan hierarchikus adatszerkezet, amelyben minden csomópontnak legfeljebb két gyermeke van. Előny, hogy egy ilyen adatszerkezetben a bővítés rugalmas, gyorsan (O(log n) időben) lehet keresni, beszúrni és törölni. De vannak hátrányok is. Például ha a beszúrások nem megfelelő sorrendben történnek, a fa „elnyúlhat” és láncolt listává válhat, illetve nem ideálisak a tömbökhöz képest, ha sokszor kell index alapján elérni elemet.