1. Kolekcja OrderedDictionary
W .NET zawsze doceniano bogatą bibliotekę standardową, ale w życiu programisty zdarzały się kompromisy: chciałoby się słownik, który pamięta kolejność dodawania elementów, albo potrzebny jest zbiór niezmiennych unikalnych wartości. Przed .NET 9 trzeba było korzystać z zewnętrznych bibliotek albo wymyślać własne rozwiązania (niektórzy nawet otwierali GitHub i kopiowali OrderedDictionary stamtąd — ciii, nikomu nie powiemy).
Z pojawieniem się .NET 9 mamy nowe uniwersalne kolekcje — można zapomnieć o prowizorkach! Zobaczmy szczegółowo dwie najbardziej przydatne: OrderedDictionary<TKey, TValue> i ReadOnlySet<T>.
1. Czym jest OrderedDictionary?
OrderedDictionary to hybryda słownika i listy. Przechowuje pary "klucz-wartość", jak zwykły Dictionary<TKey, TValue>, ale gwarantuje, że kolejność elementów odpowiada kolejności ich dodania. To szczególnie ważne, gdy trzeba przechodzić po elementach w tej samej kolejności, w jakiej użytkownik je wprowadził, albo gdy kolejność wpływa na logikę biznesową: drukowanie raportów, serializacja danych, generowanie konfiguracji.
AnalogiaJeśli zwykły słownik to szafa z mnóstwem przegródek, gdzie można szybko wrzucać i wyjmować rzeczy bez myślenia o kolejności, to OrderedDictionary to uporządkowana szafka z wysuwanymi szufladami. Wszystko leży po kolei i zawsze wiesz, co włożyłeś najpierw, a co potem.
2. Główna różnica względem Dictionary
- Dictionary: kolejność elementów nie jest gwarantowana (nawet jeśli wydaje się, że zawsze jest tak samo — nie wierz, to złudzenie!).
- OrderedDictionary: elementy są przechowywane dokładnie w tej kolejności, w jakiej zostały dodane.
3. Składnia i podstawowe metody
Oto podstawowy przykład użycia:
using System.Collections.Generic;
var od = new OrderedDictionary<string, int>();
od.Add("Ivan", 5);
od.Add("Svetlana", 8);
od.Add("Alex", 3);
// Iteracja w kolejności dodania:
foreach (var pair in od)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
Wynik:
Ivan: 5
Svetlana: 8
Alex: 3
Jeśli spróbujesz tego samego z normalnym słownikiem, kolejność często będzie inna. A z OrderedDictionary — zawsze dokładnie tak, jak dodałeś.
OrderedDictionary implementuje te same interfejsy co zwykły słownik:
- IDictionary<TKey, TValue>
- IReadOnlyDictionary<TKey, TValue>
- IEnumerable<KeyValuePair<TKey, TValue>>
4. Dostęp po indeksie i po kluczu
Ciekawostka: OrderedDictionary ma indeksator zarówno po kluczu jak i po indeksie!
// Po nazwie (kluczu):
int svetlanaScore = od["Svetlana"]; // 8
// Po indeksie:
var firstEntry = od.ElementAt(0); // KeyValuePair<string, int>("Ivan", 5)
5. Aktualizacja i usuwanie
Jeśli dodasz nowy element z istniejącym kluczem, zostanie rzucony wyjątek. Jeśli chcesz podmienić wartość — użyj indeksatora:
od["Ivan"] = 10; // Zmieni istniejący, kolejność zostaje ta sama
Możesz usuwać zarówno po kluczu, jak i po indeksie:
od.Remove("Svetlana");
od.RemoveAt(0); // usuwa "Ivan"
6. Wizualizacja struktury
flowchart LR
A("0: Ivan - 5")
B("1: Svetlana - 8")
C("2: Alex - 3")
A --> B --> C
7. Pułapki i błędy
Wielu programistów próbuje używać Dictionary<TKey, TValue> i liczy na kolejność — to pułapka! Nawet jeśli na jednych danych kolejność się zgadza, następnym razem może być inna (szczególnie po zmianach w .NET albo na innej platformie).
W OrderedDictionary przez przechowywanie kolejności niektóre operacje (wstawianie, usuwanie ze środka) są minimalnie wolniejsze niż w zwykłym słowniku, ale w większości przypadków zalety przeważają.
Jeśli często musisz szukać po indeksie — to właśnie OrderedDictionary, a jeśli zależy ci tylko na szybkim wyszukiwaniu po kluczu i nie obchodzi cię kolejność — użyj zwykłego słownika.
8. Praktyczny przykład dla aplikacji
Załóżmy, że w naszej aplikacji do ewidencji pracowników i działów potrzebny jest raport, gdzie pracownicy są wypisywani w kolejności, w jakiej zostali dodani:
var employeeScores = new OrderedDictionary<string, int>();
employeeScores.Add("Piotr", 100);
employeeScores.Add("Anna", 150);
employeeScores.Add("Wiktoria", 80);
// Teraz raport zawsze w odpowiedniej kolejności:
foreach (var pair in employeeScores)
{
Console.WriteLine($"{pair.Key}: {pair.Value} punktów");
}
2. Kolekcja ReadOnlySet<T>
1. Czym jest ReadOnlySet?
ReadOnlySet<T> to niezmienny (immutable) zbiór unikalnych wartości. Kiedyś, żeby stworzyć "set" tylko do odczytu, trzeba było zwracać kopię przez .ToHashSet() albo pisać własny wrapper. Teraz mamy kolekcję, do której nie można dodać ani usunąć elementu po utworzeniu. To zwiększa bezpieczeństwo kodu i zapobiega przypadkowym błędom przy zmianach danych z zewnątrz.
AnalogiaTo jak notes, w którym zapisałeś unikalne imiona i... zalaminowałeś strony. Nikt już nic nie dopisze, nie wyrwie i nie poprawi.
2. Tworzenie ReadOnlySet
Najprostszy sposób na stworzenie niezmiennego zbioru — użyć metody rozszerzającej:
var colors = new[] { "Red", "Green", "Blue", "Green" };
var readOnlyColors = colors.ToReadOnlySet();
// Teraz są tu tylko unikalne wartości i nie da się ich zmienić:
foreach (var color in readOnlyColors)
Console.WriteLine(color);
Wynik:
Red
Green
Blue
Możesz też użyć bezpośredniego konstruktora (jeśli trzeba):
var set = new ReadOnlySet<int>(new[] {1, 2, 2, 3, 5, 1}); // I tak będzie 1,2,3,5
3. Podstawowe właściwości i metody
- Tylko do odczytu (immutable)
- Count — liczba elementów
- Contains(item) — sprawdzenie, czy element istnieje
- Obsługuje zapytania LINQ (IEnumerable<T>)
- Szybkie wyszukiwanie, ale nie można dodawać, usuwać, czyścić
Przykład:
if (readOnlyColors.Contains("Red"))
Console.WriteLine("Czerwony jest na liście!");
Nie można zrobić tak:
// Błąd kompilacji!
readOnlyColors.Add("Yellow");
4. Gdzie się to przyda?
Bardzo często musisz udostępnić na zewnątrz dane jako zbiór, ale nie chcesz, żeby ktoś przypadkiem (albo złośliwie) go zmienił. Na przykład:
- Zwracamy listę obsługiwanych ról użytkownika
- Lista dozwolonych rozszerzeń plików
- Zbiór unikalnych parametrów konfiguracyjnych, których nie wolno zmieniać
Przykład w naszej aplikacji: załóżmy, że mamy listę dozwolonych działów:
public static ReadOnlySet<string> Departments { get; } =
new[] { "Kadry", "Rozwój", "Księgowość" }.ToReadOnlySet();
Teraz nikt nie podmieni listy działów z zewnątrz.
5. Wizualizacja: różnica względem zwykłego HashSet
graph LR
A[HashSet] -- Add/Remove/Contains --> B((Elements))
C[ReadOnlySet] -- Only Contains --> D((Elements))
3. OrderedDictionary vs. ReadOnlySet: tabela różnic
| Kolekcja | Przechowuje pary? | Gwarantuje kolejność | Można zmieniać | Wyszukiwanie po kluczu | Wyszukiwanie po indeksie | Scenariusze |
|---|---|---|---|---|---|---|
| OrderedDictionary | Tak "klucz-wartość" | Tak | Tak | Tak | Tak | Mapy, ustawienia, raporty |
| ReadOnlySet | Nie, tylko wartości | Nie | Nie | Tak | Nie | Bezpieczne zbiory, stałe |
| HashSet | Nie, tylko wartości | Nie | Tak | Tak | Nie | Zbiory, gdzie potrzebna jest modyfikacja |
| Dictionary | Tak "klucz-wartość" | Nie | Tak | Tak | Nie | Mapy bez wymagań co do kolejności |
GO TO FULL VERSION