1. Wprowadzenie
Wyobraź sobie, że jesteś właścicielem małego sklepiku i masz listę towarów. Każdy towar ma swój unikalny kod (np. ART-001, ART-002) i oczywiście nazwę, cenę, ilość na magazynie.
Gdybyś trzymał to wszystko w List<T>, gdzie T to na przykład nasza przyszła klasa Product, to żeby znaleźć towar z kodem ART-005, musiałbyś przeszukać całą listę:
"To ART-001? Nie. To ART-002? Nie... A to ART-005! Znalazłem!"
Przy 10 towarach to nie problem. Ale jeśli masz 10 000 towarów? Albo 100 000? Szukanie każdego produktu zajmie strasznie dużo czasu. Twój klient będzie musiał bardzo długo czekać, aż znajdziesz jego ulubioną paczkę ciastek. Słabo!
Potrzebujemy sposobu, który pozwoli nam natychmiast przejść do potrzebnego towaru, jeśli znamy jego unikalny kod, bez przeszukiwania wszystkiego. Czyli potrzebujemy jakiegoś "klucza", który wskaże prosto na odpowiednią "wartość".
Poznanie Dictionary
I tu na scenę wchodzi Dictionary<TKey, TValue>! Wyobraź sobie, że to nie jest zwykła lista, tylko bardzo sprytna książka telefoniczna. W zwykłej książce telefonicznej szukasz numeru telefonu (wartość) po imieniu osoby (klucz). Otwierasz książkę na literę "A", potem szukasz "Aleksy", i masz jego numer. Nie musisz przeglądać wszystkich numerów po kolei, aż znajdziesz ten właściwy.
Dokładnie tak samo Dictionary (czyli "Słownik" po angielsku) przechowuje dane jako pary "klucz-wartość".
- Klucz (TKey): To unikalny identyfikator dla każdego elementu. Jest jak imię w książce telefonicznej albo kod produktu. Po tym kluczu będziesz szukać potrzebnej wartości. Klucz musi być unikalny w obrębie jednego słownika. Jeśli spróbujesz dodać element z już istniejącym kluczem, Dictionary ci na to nie pozwoli.
- Wartość (TValue): To są dane, które chcesz przechowywać. Może to być numer telefonu, cena produktu, opis pojęcia — cokolwiek!
Litery TKey i TValue w nawiasach ostrych <TKey, TValue> oznaczają, że Dictionary to kolekcja generyczna (Generic). Sam decydujesz, jaki typ danych będzie twoim kluczem, a jaki wartością. Może to być string jako klucz (imię, kod), int (ID użytkownika), albo nawet twoja własna klasa. Wartością może być int, string, double albo znowu cały obiekt.
Zaleta? Natychmiastowy dostęp! Dzięki specjalnej wewnętrznej strukturze (hash-table, jeśli chcesz być naukowy, ale spokojnie, nie musisz jeszcze wiedzieć jak to działa), Dictionary pozwala znaleźć wartość po kluczu w bardzo krótkim czasie, niezależnie czy masz w słowniku dziesięć elementów czy milion. To jak super-indeks w ogromnej bibliotece: mówisz "potrzebuję książki o C#", i od razu pokazują ci, gdzie leży, bez przeglądania każdej półki.
No to do roboty! Będziemy rozwijać nasz projekt, tworząc interaktywny "Słowniczek terminów C#", który pomoże nam samym zapamiętywać nowe pojęcia.
2. Podstawy składni: tworzymy słownik
Wszystko zaczyna się tradycyjnie: deklarujemy zmienną, tylko teraz nie zapominamy podać nie jednego, a dwa typy — typ klucza (TKey) i typ wartości (TValue):
// Prosty słownik: klucz - string (login), wartość - string (email)
Dictionary<string, string> userEmails = new Dictionary<string, string>>();
// Albo krócej z var
var userEmails = new Dictionary<string, string>();
Dlaczego trzeba podać oba typy?
Bo C# to język o ścisłej typizacji i słownik musi wiedzieć, jakie typy kluczy i wartości dla niego przygotowałeś.
Dodawanie elementów
Żeby dodać nową parę "klucz-wartość", używamy metody Add. Klucz musi być unikalny!
userEmails.Add("vasya", "vasya@example.com");
userEmails.Add("petya", "petya@gmail.com");
Jeśli spróbujesz dodać jeszcze raz z tym samym kluczem — słownik się obrazi i rzuci wyjątek.
Dostęp do wartości po kluczu
Najfajniejsze w słowniku — pobieranie wartości po kluczu:
string email = userEmails["vasya"];
Console.WriteLine(email); // vasya@example.com
Jeśli odwołasz się do nieistniejącego klucza, program od razu krzyknie (wywali wyjątek KeyNotFoundException). Żeby pracować bezpiecznie, zaraz omówimy metody do sprawdzania obecności klucza.
Zmiana wartości po kluczu
Jeśli taki klucz już jest, po prostu przypisujesz nową wartość:
userEmails["vasya"] = "vasya@newmail.ru"; // teraz email Vasya się zmienił
Jeśli klucza nie było — takie przypisanie stworzy nowy element w słowniku.
Przykład z użytkownikami
Dopracujmy trochę naszą aplikację edukacyjną. Trzymaliśmy listę zadań (List<string> tasks;) dla programu ToDo. Załóżmy, że teraz chcemy dodać "autoryzację": każdy użytkownik potrzebuje emaila.
Tak to może wyglądać:
// UserId — to string, Email też string
var users = new Dictionary<string, string>();
users.Add("admin", "admin@myapp.com");
users.Add("alice", "alice@wonderland.com");
users.Add("bob", "bob@builder.com");
Teraz zawsze możesz szybko sprawdzić email dowolnego użytkownika po jego loginie:
Console.WriteLine(users["alice"]); // => alice@wonderland.com
3. Podstawowe metody i właściwości Dictionary
| Metoda/Właściwość | Opis |
|---|---|
|
Dodaje nową parę "klucz-wartość". |
|
Usuwa element po kluczu. |
|
Sprawdza, czy taki klucz istnieje. |
|
Sprawdza, czy taka wartość istnieje (wolno!). |
|
Bezpiecznie pobiera wartość po kluczu, bez rzucania wyjątków. |
|
Ilość par "klucz-wartość" w słowniku. |
|
Kolekcja wszystkich kluczy. |
|
Kolekcja wszystkich wartości. |
Sprawdzanie obecności klucza
Najczęstsze (i najbezpieczniejsze) — najpierw sprawdzić, czy klucz istnieje:
if (users.ContainsKey("dasha"))
{
Console.WriteLine(users["dasha"]);
}
else
{
Console.WriteLine("Użytkownik dasha nie znaleziony!");
}
Bezpieczny sposób: TryGetValue
Metoda TryGetValue pozwala uniknąć wyjątku:
if (users.TryGetValue("bob", out string email))
{
Console.WriteLine($"Email Bob: {email}");
}
else
{
Console.WriteLine("Bob nie znaleziony!");
}
To dobra praktyka i na rozmowach kwalifikacyjnych lubią o to pytać — ucz się od razu! Co więcej, ta metoda działa szybciej niż tandem ContainsKey + dostęp po indeksie.
4. Przechodzenie po słowniku: pętla foreach
Jeśli chcesz przejść po wszystkich parach, użyj pętli foreach. Każdy element słownika to obiekt typu KeyValuePair<TKey, TValue>:
foreach (var pair in users)
{
Console.WriteLine($"Login: {pair.Key}, Email: {pair.Value}");
}
Albo, jeśli chcesz być jeszcze bardziej na czasie:
foreach (var (login, email) in users)
{
Console.WriteLine($"{login}: {email}");
}
// Taka składnia jest dostępna dzięki dekompozycji krotek (C# 7+).
5. Usuwanie i zmiana wartości
Usunąć użytkownika po loginie jest prosto:
users.Remove("alice");
Jeśli takiego klucza nie ma — zwróci false. Możesz spokojnie próbować usuwać, bez obaw o wyjątek.
Zmienić email użytkownika:
users["bob"] = "bob@constructor.com";
Jeśli takiego klucza nie było — pojawi się nowa para!
6. Przydatne właściwości Keys i Values
Jeśli potrzebujesz tylko listy loginów (kluczy) albo tylko emaili (wartości), użyj kolekcji Keys i Values:
foreach (string login in users.Keys)
{
Console.WriteLine("Login: " + login);
}
foreach (string email in users.Values)
{
Console.WriteLine("Email: " + email);
}
7. Ważne niuanse słownika
Klucze muszą być unikalne
Czyli nie możesz dodać dwóch takich samych kluczy. Jeśli spróbujesz — dostaniesz wyjątek. Taka unikalność zapewnia bezpieczeństwo danych: jeden użytkownik nie może mieć dwóch emaili (po jednym wpisie).
Klucz nie może być null (dla string)
Dla kluczy typu string próba dodania klucza null wywoła błąd (ArgumentNullException). Jeśli nagle nie masz klucza, zastanów się — może to znak, że coś jest nie tak z logiką danych.
Dlaczego wyszukiwanie w słowniku jest takie szybkie?
Dictionary działa "pod maską" na hash-table. To znaczy, że wyszukiwanie po kluczu to nie przeglądanie wszystkich elementów po kolei, tylko błyskawiczne obliczenie specjalnej "hash-funkcji" i praktycznie bezpośredni dostęp do komórki, gdzie leży wartość.
Co można użyć jako klucz?
- Dowolny typ, dla którego poprawnie działa porównanie równości i uzyskanie unikalnego kodu (metody Equals i GetHashCode()).
- Zwykle to string, int, Guid albo twoje własne typy (ale wtedy trzeba uważać z nadpisywaniem Equals/GetHashCode, bo można złapać zabawne bugi).
8. Dodajemy słownik do aplikacji
W naszym mini-aplikacji ToDo dodamy słownik użytkowników i zrobimy funkcję wyszukiwania emaila po loginie z obsługą błędów:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Słownik użytkowników: login => email
var users = new Dictionary<string, string>
{
{ "admin", "admin@myapp.com" },
{ "alice", "alice@wonderland.com" },
{ "bob", "bob@builder.com" }
};
Console.WriteLine("Podaj login użytkownika do wyszukania emaila:");
string login = Console.ReadLine();
// Bezpieczne wyszukiwanie emaila
if (users.TryGetValue(login, out string email))
{
Console.WriteLine($"Email użytkownika {login}: {email}");
}
else
{
Console.WriteLine($"Użytkownik {login} nie znaleziony.");
}
// Przegląd wszystkich użytkowników
Console.WriteLine("\nLista wszystkich użytkowników:");
foreach (var pair in users)
{
Console.WriteLine($"{pair.Key} => {pair.Value}");
}
}
}
9. Typowe błędy i pułapki początkujących
Czasem bardzo kusi, żeby zrobić tak:
// Licząc, że jeśli klucza nie ma — wszystko będzie ok
string value = users["nonexistent"]; // Bum! KeyNotFoundException!
Nie zapominaj: zawsze sprawdzaj obecność klucza (ContainsKey albo TryGetValue), jeśli nie masz pewności, że na pewno istnieje.
Pamiętaj też: przeglądanie wartości nie gwarantuje ich unikalności! Ten sam email może być u dwóch loginów (jeśli stracisz kontrolę nad unikalnością wartości, a nie kluczy).
Myli się też metody — na przykład próbują usuwać po wartości:
users.Remove("bob@builder.com"); // Nie usunie! Oczekiwany jest klucz, nie wartość.
GO TO FULL VERSION