Kolekce se v programovacích jazycích používají pro uložení většího množství dat na jedno místo. Nejjednodušší kolekcí je statické pole, tedy pole, u kterého je jeho velikost známá v době kompilace a po vytvoření se již nemění. Tato situace je ale v reálném světě velmi málo pravděpodobná. Pro představu lze uvést příklad nákupního košíku v eshopu. Každý uživatel chce nakoupit jiné množství položek, takže zvolení konkrétní velikosti košíku, která by vyhovovala všem uživatelům, je skoro nemožné.
Pokud je potřebné se za každou cenu vyhnout využití kolekcí, které podporují dynamickou velikost, tak by bylo nutné zvolit velmi velkou velikost košíku – např. 1 000 000 položek. To ale znamená, že v paměti prohlížeče bude muset být alokována paměť pro všechny položky, i pokud je v košíku pouze jeden předmět. To je zbytečné a může to způsobovat výkonnostní problémy prohlížeče.
Dynamické kolekce
Dynamické kolekce jsou kolekce, které umožňují uložení teoreticky libovolného počtu prvků. Prakticky je velikost limitována velikostí operační paměti, architekturou systému, verzí .NET a dalšími faktory.
Pro kolekce je běžné, že se jedná o generické datové typy (více v Generika), tedy je možné do nich uložit všechny datové typy. Níže jsou uvedeny základní reprezentanti dynamických kolekcí:
Seznam
Fronta
Zásobník
Slovník
.NET obsahuje velké množství již implementovaných a zároveň velice optimalizovaných kolekcí, které jsou připraveny k použití. Všechny se nacházejí v namespace System.Collections.Generic.
Info
Více informací o dynamických kolekcích a dalších abstraktních datových typech lze nalézt na stránce Abstraktní datové typy.
Seznam – Dynamické “nafukovací” pole
Problémem polí o fixní velikosti je to, že je nutné, předem vědět jejich cílovou velikost. Pokud není známá, tak je možné nastavit maximální velikost (dle konkrétních požadavků zadání), ale znamená to, že aplikace po celou dobu v paměti musí toto místo držet. Pak se tedy stává to, že i jednoduchá aplikace může zabírat například 3 GB RAM.
Alternativou je dynamické pole, u kterého je možné za běhu měnit jeho velikost. Toto přináší optimálnější využití paměti, ale je to kompenzováno vyšší režií potřebnou pro překopírování prvků do zvětšené kolekce.
Dynamické pole lze implementovat různými způsoby, nejčastěji využívaná implementace využívá statické pole. Pokud by mělo dojít k umístění prvku za hranici pole, tak dojde k vytvoření nového pole o dvojnásobné velikosti a překopírování původního pole do nového pole. Původní pole je pak automaticky odstraněno
V .NET je možné využít generický datový typ List<T>, který toto chování obsahuje a jeho implementace je velmi optimalizovaná, proto se nevyplatí tvořit vlastní List pro jiné než edukativní účely.
List<string> fruits = new List<string>();// Přidání prvků do listufruits.Add("Apple");fruits.Add("Banana");fruits.Add("Cherry");// Výpis ovoceConsole.WriteLine("Fruits in the list:");foreach (string fruit in fruits){ Console.WriteLine(fruit);}// Odstranění položky: Banánfruits.Remove("Banana");// Přístup k první položce (tedy položce na indexu 0)Console.WriteLine($"First fruit in the list: {fruits[0]}");// Výpis počtu položek listuConsole.WriteLine($"Total fruits: {fruits.Count}");
Fronta
Datový typ, který umožňuje uložení prvků v pořadí, ve kterém byly do fronty vloženy. Tento princip je také nazýván jako FIFO (First-in-First-out). V .NET lze využít implementaci Queue<T>.
Queue<string> queue = new Queue<string>();// Přidání prvků do frontyqueue.Enqueue("První");queue.Enqueue("Druhý");queue.Enqueue("Třetí");// Zobrazení počtu prvkůConsole.WriteLine($"Počet prvků ve frontě: {queue.Count}");// Nahlédnutí na první prvek bez odebráníConsole.WriteLine($"První prvek ve frontě (Peek): {queue.Peek()}");// Odebrání prvku z frontystring first = queue.Dequeue();Console.WriteLine($"Odebraný prvek: {first}");// Zobrazení zbylých prvkůConsole.WriteLine("Zbývající prvky ve frontě:");foreach (string item in queue){ Console.WriteLine(item);}
Zásobník
Zásobník je datový typ umožňující ukládání prvků v pořadí, ve kterém přišly, ale na rozdíl od fronty se zpracovávají nejdříve naposledy přidané prvky (tedy prvky z vrcholu zásobníku). Zásobník funguje na principu LIFO (Last-in-First-out). V .NET lze využít typ Stack<T>.
Stack<string> stack = new Stack<string>();// Přidání prvků na zásobníkstack.Push("První");stack.Push("Druhý");stack.Push("Třetí");Console.WriteLine($"Počet prvků na zásobníku: {stack.Count}");// Nahlédnutí na vrchol zásobníku (bez odebrání)Console.WriteLine($"Vrchol zásobníku (Peek): {stack.Peek()}");// Odebrání prvku ze zásobníkustring top = stack.Pop();Console.WriteLine($"Odebraný prvek: {top}");// Výpis zbývajících prvkůConsole.WriteLine("\Zbývající prvky na zásobníku:");foreach (string item in stack){ Console.WriteLine(item);}
Slovník
Slovník je datový typ, který umožňuje ukládat dvojici klíč hodnota. Přístup k položkám je možný pouze pomocí klíče, ale velkou výhodou je, že je možné jej realizovat v konstantním čase (O(1)).
// Vytvoření slovníku (klíč = string, hodnota = int)Dictionary<string, int> ages = new Dictionary<string, int>();// Přidání položek do slovníkuages.Add("Alice", 30);ages.Add("Bob", 25);ages["Charlie"] = 35; // jiný způsob zápisu// Zjištění hodnoty podle klíčeConsole.WriteLine($"Věk Alice: {ages["Alice"]}");// Kontrola, jestli klíč existujeif (ages.ContainsKey("Bob")){ Console.WriteLine($"Věk Boba: {ages["Bob"]}");}// Iterace přes všechny prvkyConsole.WriteLine("\nVšichni lidé ve slovníku:");foreach (KeyValuePair<string, int> kvp in ages){ Console.WriteLine($"{kvp.Key}: {kvp.Value}");}// Odebrání položkyages.Remove("Alice");Console.WriteLine("\nPo odebrání Alice:");foreach (var kvp in ages){ Console.WriteLine($"{kvp.Key}: {kvp.Value}");}
Tvorba slovníku
Kromě tvorby slovníku a jeho následné naplnění hodnotami lze využít i kompatnější syntaxi.
Dictionary<string, int> ages = new(){ new KeyValuePair<string, int>("Alice", 30), // Analogicky new ("key", 30)}