1. Wprowadzenie
Bądźmy szczerzy, tablice są spoko, ale mają swoje ograniczenia. Chcesz zmienić rozmiar? Musisz stworzyć nową tablicę i ręcznie przekopiować elementy! Chcesz szybko znaleźć element po kluczu? Tablica powie: "No, spróbuj zrobić to sam".
Prawdziwe życie jest pełne scenariuszy, gdzie tablice są niewygodne. Wyobraź sobie, że piszesz apkę do ewidencji książek w bibliotece (siema, nasz mini-system edukacyjny!). Na początku masz 5 książek — tablica wystarczy. Po tygodniu już 500, a po miesiącu ktoś oddał książkę, ktoś zgubił... I tu na ratunek przychodzą kolekcje!
Kolekcje to takie kontenery, które potrafią:
- Automatycznie zmieniać swój rozmiar.
- Zapewniać szybkie wyszukiwanie, dodawanie, usuwanie.
- Dawać wygodniejsze sposoby obróbki danych: sortowanie, filtrowanie, grupowanie.
W .NET jest kilka kluczowych "rodzin" kolekcji. Dziś poznasz podstawowe: listy (List<T>), słowniki (Dictionary<TKey, TValue>), zbiory (HashSet<T>), kolejki, stosy, a także interfejsy, które są ich bazą.
2. Podstawowe rodzaje kolekcji
Chociaż jeszcze nie wchodziliśmy w szczegóły konkretnych typów kolekcji generycznych, ważne jest, żeby rozumieć, jakie są główne kategorie kolekcji i do czego się nadają. Pomyśl o tym jak o różnych typach "kontenerów", każdy z własnymi cechami:
Listy (Lists)
Co to jest? Uporządkowana sekwencja elementów. Jak zwykła lista zakupów albo lista studentów w dzienniku.
Kluczowe cechy: Elementy mają kolejność (możesz dostać się po indeksie, jak w tablicy). Mogą zawierać powtarzające się elementy. Rozmiar jest dynamiczny.
Kiedy używać? Gdy ważna jest kolejność elementów i chcesz szybko dodawać/usuwać elementy na końcu listy albo pobierać elementy po pozycji.
Przykład z życia: Kolejka do kasy, lista uczestników webinaru, sekwencja klatek w animacji.
Słowniki/Mapy (Dictionaries/Maps)
Co to jest? Zbiór par "klucz-wartość". Trochę jak prawdziwy słownik, gdzie każde słowo (klucz) ma swoje znaczenie (wartość).
Kluczowe cechy: Każdy klucz jest unikalny. Klucz służy do szybkiego wyszukiwania odpowiadającej mu wartości. Kolejność elementów zazwyczaj nie jest gwarantowana.
Kiedy używać? Gdy musisz szybko znaleźć wartość po jakimś unikalnym identyfikatorze (kluczu).
Przykład z życia: Notatnik (imię - telefon), baza produktów (ID produktu - opis), ustawienia aplikacji (nazwa ustawienia - wartość).
Zbiory (Sets)
Co to jest? Nieuporządkowany zbiór unikalnych elementów. Jak matematyczny zbiór.
Kluczowe cechy: Nie mogą zawierać powtarzających się elementów. Kolejność elementów nie jest gwarantowana. Są zoptymalizowane do sprawdzania obecności elementu i operacji na zbiorach (suma, przecięcie).
Kiedy używać? Gdy musisz przechowywać unikalne wartości i szybko sprawdzać, czy element jest w zbiorze.
Przykład z życia: Lista unikalnych odwiedzających stronę, zbiór tagów do artykułu, lista słów do autouzupełniania (bez powtórzeń).
Kolejki (Queues)
Co to jest? Kolekcja działająca na zasadzie "pierwszy przyszedł — pierwszy wyszedł" (FIFO - First-In, First-Out).
Kluczowe cechy: Elementy dodajesz na koniec kolejki, a usuwasz z początku.
Kiedy używać? Modelowanie procesów, gdzie ważna jest kolejność obsługi, np. system ticketów wsparcia, kolejka do drukarki.
Stosy (Stacks)
Co to jest? Kolekcja działająca na zasadzie "ostatni przyszedł — pierwszy wyszedł" (LIFO - Last-In, First-Out).
Kluczowe cechy: Elementy dodajesz i usuwasz z jednego końca (szczytu stosu).
Kiedy używać? Śledzenie historii akcji (cofnij w edytorze), obsługa zagnieżdżonych struktur, rekurencja.
Różnice między typami kolekcji
| Typ kolekcji | Analogia | Główna zasada | Dostęp po indeksie? | Kolejność elementów? | Duplikaty dozwolone? | Podstawowe operacje |
|---|---|---|---|---|---|---|
| Tablica | Rząd ponumerowanych komórek | Stały rozmiar | Tak | Tak | Tak | Pobieranie/ustawianie po indeksie |
| Lista | Lista zakupów | Dynamiczna, uporządkowana | Tak | Tak | Tak | Dodawanie, usuwanie, wyszukiwanie |
| Słownik | Słownik (klucz-wartość) | Unikalne klucze | Nie | Nie (zazwyczaj) | Nie (po kluczu) | Pobieranie po kluczu, dodawanie |
| Zbiór | Zbiór unikalnych obiektów | Tylko unikalne elementy | Nie | Nie | Nie | Sprawdzanie obecności, suma |
| Kolejka | Kolejka do kasy | FIFO (Pierwszy przyszedł - pierwszy wyszedł) | Nie | Tak | Tak | Dodawanie na koniec, pobieranie z początku |
| Stos | Stos talerzy | LIFO (Ostatni przyszedł - pierwszy wyszedł) | Nie | Tak | Tak | Dodawanie na górę, pobieranie z góry |
3. Lista: List<T>
Lista to najczęściej używana kolekcja w C#. Nawet częściej niż tablica. Lista — kolekcja, która przypomina tablicę, ale potrafi sama rosnąć. Możesz dodawać i usuwać elementy bez bólu głowy.
using System;
using System.Collections.Generic;
var numbers = new List<int>(); // Tworzymy pustą listę liczb całkowitych
numbers.Add(10); // Dodajemy element
numbers.Add(15);
numbers.Add(42);
Console.WriteLine(numbers[0]); // 10
numbers.Remove(15); // Usuwamy element po wartości
foreach (var number in numbers)
{
Console.WriteLine(number);
}
// Wypisze: 10 i 42
Dlaczego List<T> jest lepsza od tablicy do kolekcji danych, której rozmiaru nie znasz z góry?
— Bo nie musisz ręcznie przydzielać więcej pamięci i kopiować tablicy przy dodawaniu elementów — wszystko robi kolekcja!
Kiedy używać List<T>?
- Dynamiczna lista, gdy elementy można dodawać, usuwać, zmieniać.
- Nie potrzebujesz szybkiego wyszukiwania po kluczu (to domena słowników).
- Często używana do pracy z uporządkowanymi zbiorami.
4. Słownik Dictionary<TKey, TValue>
Tablica (i lista) pozwala przechowywać listę wartości w komórkach tablicy, a każda komórka ma indeks. A słownik to kolekcja, która pozwala zamiast liczby (indeksu) używać stringa (nazwy). Taka nazwa komórki to klucz.
Jeśli musisz po jakimś kluczu szybko dostać się do wartości (np. po numerze karty czytelnika — imię czytelnika), to potrzebujesz słownika.
using System.Collections.Generic;
var phoneBook = new Dictionary<string, string>();
phoneBook["Ania"] = "+79992221133";
phoneBook["Maksym"] = "+79998887766";
Console.WriteLine(phoneBook["Ania"]); // +79992221133
// Możesz sprawdzić, czy jest klucz:
if (phoneBook.ContainsKey("Wiktor"))
{
Console.WriteLine(phoneBook["Wiktor"]);
}
else
{
Console.WriteLine("Nie ma takiego numeru!");
}
Ciekawostka: Słowniki często nazywa się "tablicami asocjacyjnymi". Są zaimplementowane na bazie hash-table, co pozwala wyszukiwać po kluczu bardzo szybko (prawie natychmiastowo, jeśli nie liczyć kolizji — ale o tym później).
Kluczowe cechy
- Klucz jest unikalny: w słowniku nie może być dwóch takich samych kluczy.
- Wartości mogą się powtarzać.
- Bardzo szybko wyszukuje, dodaje, usuwa elementy po kluczu.
5. Zbiór: HashSet<T>
Oprócz list i słowników bardzo popularne są też zbiory. To prawie jak lista, tylko prościej: lista bez ustalonej kolejności elementów. Taki zbiór po prostu przechowuje zestaw wartości, gdy kolejność nie ma znaczenia.
Na przykład, musisz tylko wiedzieć, czy coś jest w zbiorze, bez duplikatów i nie ważna jest kolejność — wtedy używasz zbioru.
using System.Collections.Generic;
var knownUsers = new HashSet<string>();
knownUsers.Add("admin");
knownUsers.Add("guest");
knownUsers.Add("admin"); // Ponowne dodanie — ignorowane
Console.WriteLine(knownUsers.Contains("admin")); // True
Console.WriteLine(knownUsers.Count); // 2
Zbiór bardzo szybko sprawdza obecność elementu.
Po co są zbiory?
— Jeśli np. chcesz przechowywać wszystkich unikalnych użytkowników, którzy otworzyli apkę w miesiącu, albo np. listę unikalnych autorów książek.
Cechy
- Przechowuje tylko unikalne elementy (wstawienie duplikatu jest ignorowane).
- Brak indeksów jak w liście.
- Szybkie sprawdzanie obecności.
6. Kolejki i stosy
Są jeszcze struktury na specjalne okazje: kolejka i stos. W sumie to taki specjalny rodzaj listy, tylko z kontrolowanym dodawaniem i pobieraniem elementów.
Queue<T>: kolejka (first in — first out)
Czasem trzeba trzymać "kolejkę" — nowy element staje na końcu, a pobierasz z początku.
using System.Collections.Generic;
var queue = new Queue<string>();
queue.Enqueue("Pierwszy");
queue.Enqueue("Drugi");
queue.Enqueue("Trzeci");
Console.WriteLine(queue.Dequeue()); // "Pierwszy"
Console.WriteLine(queue.Peek()); // "Drugi", ale nie usuwa
Stack<T>: stos (first in — last out)
Stos działa odwrotnie: ostatni wszedł — pierwszy wyszedł. Używany w parserach, obsłudze wywołań funkcji i nawet przy cofaniu akcji w edytorach.
using System.Collections.Generic;
var stack = new Stack<string>();
stack.Push("Jeden");
stack.Push("Dwa");
stack.Push("Trzy");
Console.WriteLine(stack.Pop()); // "Trzy"
Console.WriteLine(stack.Peek()); // "Dwa"
7. Tabela: porównanie podstawowych kolekcji
| Kolekcja | Unikalne elementy | Dostęp po indeksie | Szybkie wyszukiwanie po kluczu | Operacje wstawiania/usuwania |
|---|---|---|---|---|
|
Nie | Tak | Nie | Szybko na koniec |
|
Klucze | Nie | Tak | Szybko po kluczu |
|
Tak | Nie | Tak* | Szybko po wartości |
|
Nie | Nie | Nie | Szybko (FIFO) |
|
Nie | Nie | Nie | Szybko (LIFO) |
Każdą kolekcję omówimy dokładniej na kolejnych wykładach. A zaczniemy od tajemniczej litery <T>...
GO TO FULL VERSION