CodeGym /Kursy /C# SELF /Kolekcje: OrderedDictionar...

Kolekcje: OrderedDictionary i ReadOnlySet

C# SELF
Poziom 34 , Lekcja 1
Dostępny

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.

Analogia

Jeś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
Każdy węzeł to para klucz-wartość, a strzałki pokazują kolejność dodania.

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.

Analogia

To 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

ReadOnlySet w Microsoft Docs

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))
HashSet można zmieniać, ReadOnlySet — nigdy!

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
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION