Każda poważna aplikacja w pewnym momencie musi zarządzać nie jednym obiektem, ale wieloma — listą zamówień, koszem klientów, kolejką zadań do przetworzenia, cache’em produktów po ID. Jak je przechowujesz, decyduje o tym, czy Twój kod będzie szybki, czytelny i skalowalny — czy będziesz debugował dziwne bugi o 2 w nocy.
Ten wpis to mapa kolekcji w .NET — punkt startowy, z którego przejdziesz do szczegółowego materiału dla każdego typu. Jeśli dopiero zaczynasz, zacznij tu. Jeśli szukasz konkretnej kolekcji — przejdź od razu do linków na końcu.

Czym jest kolekcja?
Kolekcja to typ zaprojektowany do grupowania powiązanych obiektów i zarządzania nimi jako jedną całością. Zamiast tworzyć sto zmiennych order1, order2… order100, przechowujesz je w jednej kolekcji i operujesz na nich: dodajesz, usuwasz, przeszukujesz, sortujesz, iterujesz.
W .NET kolekcje żyją głównie w przestrzeni nazw System.Collections.Generic — to generyczne (typowane) odpowiedniki starszych, niegeneryckach klas z System.Collections. W nowym kodzie zawsze używasz wariantów generycznych: List<T>, Dictionary<TKey, TValue> itd.
Mapa kolekcji w .NET — szybki przegląd
Pięć podstawowych typów, które pokrywają 95% przypadków w typowej aplikacji biznesowej:
1. Tablica — T[]
Stały rozmiar, maksymalna wydajność. Element wewnętrzny niemal wszystkich innych kolekcji. Używasz jej gdy znasz rozmiar z góry i nie będziesz dodawać ani usuwać elementów.
string[] months = { "Styczeń", "Luty", "Marzec", /* ... */ "Grudzień" };
Console.WriteLine(months[0]); // Styczeń
Console.WriteLine(months.Length); // 12
foreach (var month in months)
Console.WriteLine(month);
👉 Tablica w C# — szczegółowy wpis · Array vs List — kiedy co wybrać?
2. Lista — List<T>
Dynamiczny rozmiar, elastyczność. Domyślny wybór gdy nie wiesz ile elementów będziesz przechowywać. Wewnętrznie to tablica, która automatycznie rośnie.
var orders = new List<string>();
orders.Add("Zamówienie #001");
orders.Add("Zamówienie #002");
orders.Add("Zamówienie #003");
orders.Remove("Zamówienie #002");
Console.WriteLine(orders.Count); // 2
// Wyszukiwanie z LINQ
var found = orders.FirstOrDefault(o => o.Contains("#003"));
Console.WriteLine(found); // Zamówienie #003
👉 List w C# — praktyczny przewodnik · Lista — wydajność i pułapki
3. Słownik — Dictionary<TKey, TValue>
Błyskawiczne wyszukiwanie po kluczu — O(1). Zamiast przeszukiwać całą listę, podajesz klucz i od razu dostajesz wartość. Używasz gdy masz unikalne identyfikatory: ID użytkownika, kod produktu, nazwa konfiguracji.
var productCache = new Dictionary<int, string>
{
[1001] = "Klawiatura mechaniczna",
[1002] = "Monitor 27 cali",
[1003] = "Mysz bezprzewodowa"
};
// Szybki dostęp po kluczu
Console.WriteLine(productCache[1002]); // Monitor 27 cali
// Bezpieczny odczyt — TryGetValue zamiast [key], żeby nie rzucić wyjątku
if (productCache.TryGetValue(9999, out string? name))
Console.WriteLine(name);
else
Console.WriteLine("Produkt nie istnieje");
👉 Dictionary w C# — pełny przewodnik
4. HashSet — HashSet<T>
Unikalne elementy, szybkie sprawdzanie przynależności. Idealne gdy chcesz wyeliminować duplikaty albo błyskawicznie sprawdzić, czy element już istnieje.
var visitedPages = new HashSet<string>();
visitedPages.Add("/home");
visitedPages.Add("/products");
visitedPages.Add("/home"); // duplikat — cicho zignorowany
Console.WriteLine(visitedPages.Count); // 2
Console.WriteLine(visitedPages.Contains("/home")); // True
// Operacje zbiorowe
var premiumUsers = new HashSet<int> { 1, 2, 3 };
var activeUsers = new HashSet<int> { 2, 3, 4, 5 };
premiumUsers.IntersectWith(activeUsers);
// premiumUsers = { 2, 3 } — aktywni premium
👉 HashSet w C# — szczegółowy wpis · SortedSet — unikalne i posortowane
5. Kolejka i Stos — Queue<T> i Stack<T>
Kolejność dostępu jest kluczowa. Kolejka (FIFO: first-in, first-out) przetwarza elementy w kolejności dodania. Stos (LIFO: last-in, first-out) — od końca.
// Queue — FIFO: przetwarzaj w kolejności napływania
var emailQueue = new Queue<string>();
emailQueue.Enqueue("jan@example.com");
emailQueue.Enqueue("anna@example.com");
string next = emailQueue.Dequeue(); // jan@example.com (pierwszy wyszedł)
string peek = emailQueue.Peek(); // anna@example.com (podgląd bez usuwania)
// Stack — LIFO: cofanie operacji, historia nawigacji
var history = new Stack<string>();
history.Push("/home");
history.Push("/products");
history.Push("/cart");
string current = history.Pop(); // /cart (ostatni wyszedł)
string prev = history.Peek(); // /products
👉 Queue w C# — szczegółowy wpis · Stack w C# — szczegółowy wpis
Tabela decyzyjna — co wybrać?
| Potrzebujesz… | Wybierz | Dlaczego |
|---|---|---|
| Stały zestaw, znany rozmiar, max wydajność | T[] | Minimalny narzut, ciągły blok pamięci |
| Lista, rozmiar zmienia się dynamicznie | List<T> | Domyślny wybór — elastyczna, bogata w metody |
| Szybkie wyszukiwanie po unikalnym kluczu | Dictionary<TKey, TValue> | Haszowanie → O(1) lookup |
| Unikalne elementy, sprawdzanie przynależności | HashSet<T> | O(1) Contains, eliminacja duplikatów |
| Unikalne elementy i zawsze posortowane | SortedSet<T> | BST w środku → O(log n), automatyczna kolejność |
| Przetwarzaj w kolejności FIFO (zadania, eventy) | Queue<T> | Dequeue od początku — gwarantuje kolejność |
| Cofanie operacji, stos wywołań (LIFO) | Stack<T> | Pop z wierzchołka — ostatni wszedł, pierwszy wyszedł |
| Dostęp z wielu wątków jednocześnie | ConcurrentBag<T> / ConcurrentQueue<T> | Thread-safe bez ręcznych locków |
| Kolekcja tylko do odczytu (niezmienialność) | ImmutableList<T> / IReadOnlyList<T> | Bezpieczna do zwrócenia z API, bez ryzyka mutacji |
Złożoność operacji — skrótowo
Wybór kolekcji to decyzja o wydajności. Kilka kluczowych liczb:
| Operacja | T[] | List<T> | Dictionary | HashSet |
|---|---|---|---|---|
| Dostęp po indeksie | O(1) | O(1) | — | — |
| Dodaj na końcu | — | O(1)* | O(1)* | O(1)* |
| Usuń ze środka | — | O(n) | O(1)* | O(1)* |
| Wyszukaj (Contains) | O(n) | O(n) | O(1)* | O(1)* |
| Wyszukaj po kluczu | — | — | O(1)* | — |
* amortyzowane O(1) — sporadyczne realokacje mogą być droższe, ale uśredniony koszt jest stały.
💡 Jeśli masz wiele operacji Contains na dużej kolekcji — różnica między
List<T>(O(n)) aHashSet<T>/Dictionary(O(1)) może być przepaść między setkami ms a mikrosekundami.
Wspólna cecha wszystkich kolekcji — IEnumerable
Wszystkie kolekcje w .NET implementują interfejs IEnumerable<T>. To oznacza, że możesz po każdej iterować pętlą foreach i stosować LINQ:
// Działa dla T[], List<T>, Dictionary, HashSet, Queue, Stack...
IEnumerable<int> source = GetAnyCollection();
var filtered = source
.Where(x => x > 10)
.OrderByDescending(x => x)
.Take(5)
.ToList();
To właśnie dlatego możesz pisać jedną metodę akceptującą IEnumerable<T> i przekazywać do niej dowolną kolekcję — jest to najszerszy, najmniej restrykcyjny kontrakt, gdy potrzebujesz tylko iteracji.
👉 Pętla foreach — jak efektywnie iterować przez kolekcje
Co dalej?
Ten wpis to mapa. Teraz wybierz kolekcję, której potrzebujesz, i przejdź do dedykowanego materiału:
- 📋 Tablice i kolekcje w C# — kompleksowy przegląd
- 📊 Array vs List — porównanie z benchmarkiem
- 📝 List<T> — praktyczny przewodnik
- 🔍 Dictionary — szybkie wyszukiwanie po kluczu
- ✅ HashSet — unikalne elementy i operacje zbiorów
- 📬 Queue — kolejka FIFO
- 📚 Stack — stos LIFO
- 🏗️ Podstawowe struktury danych — tablice, listy, drzewa, grafy
Chcesz opanować kolekcje od zera do produkcji?
Jeśli wolisz uczyć się w ustrukturyzowany sposób — z ćwiczeniami, quizami i przykładami z realnych projektów — sprawdź kurs:
👉 C# Wprowadzenie do Kolekcji — tablica, lista, stos, kolejka, słownik, kiedy co stosować i jak nie popełniać typowych błędów wydajnościowych.
A jeśli dopiero zaczynasz przygodę z C# — ten kurs postaw jako punkt startowy:

