🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Enum w C# — kompletny przewodnik z pułapkami, o których nikt nie mówi

public class ZamowienieZle
{
    public string Status { get; set; } // "Nowe", "Wysłane", "Anulowane"... albo "wysłane", "Wysłano", literówka?
}

Reprezentowanie skończonego zbioru opcji jako stringa albo liczby to zaproszenie do błędów: kompilator nie sprawdzi literówki w "Wysłąne", IntelliSense nie podpowie dostępnych wartości, a porównanie status == "wysłane" po cichu zwróci false przez różnicę wielkości liter. enum rozwiązuje dokładnie ten problem.

Podstawowa składnia

public enum OrderStatus
{
    Nowe,       // 0
    Oplacone,   // 1
    Wyslane,    // 2
    Anulowane   // 3
}

var status = OrderStatus.Wyslane;
Console.WriteLine(status);          // "Wyslane"
Console.WriteLine((int)status);     // 2

Każda wartość enum domyślnie dostaje kolejny numer int, zaczynając od 0. Kompilator pilnuje, żeby zmienna typu OrderStatus mogła przyjąć tylko jedną z czterech zdefiniowanych nazw — literówka w OrderStatus.Wyslne nie skompiluje się, w przeciwieństwie do stringa.

Jawne wartości i typ bazowy

public enum HttpStatusKod : short // domyślnie enum bazuje na int, tu wymuszamy short (mniej pamięci)
{
    Ok = 200,
    NieZnaleziono = 404,
    BladSerwera = 500
}

Możesz jawnie przypisać wartości liczbowe (przydatne, gdy enum odzwierciedla istniejący standard, jak kody HTTP) i zmienić typ bazowy z domyślnego int na byteshortlong itd. — istotne, gdy enum trafia do bazy danych albo protokołu sieciowego, gdzie liczy się każdy bajt.

Switch na enum

string OpisStatusu(OrderStatus status) => status switch
{
    OrderStatus.Nowe => "Czeka na płatność",
    OrderStatus.Oplacone => "Przygotowywane do wysyłki",
    OrderStatus.Wyslane => "W drodze",
    OrderStatus.Anulowane => "Anulowane",
    _ => throw new ArgumentOutOfRangeException(nameof(status))
};

Analizator w Visual Studio/Rider ostrzeże, jeśli switch nie obsłuży wszystkich wartości enum — dodanie nowej wartości do OrderStatus bez zaktualizowania tego switcha da widoczne ostrzeżenie, zamiast cichego pominięcia przypadku w runtime.

[Flags] — enum jako kombinacja bitów

[Flags]
public enum Uprawnienia
{
    Brak = 0,
    Odczyt = 1,      // 0001
    Zapis = 2,       // 0010
    Usuwanie = 4,    // 0100
    Administrator = 8 // 1000
}

var uprawnienia = Uprawnienia.Odczyt | Uprawnienia.Zapis; // łączenie flag bitowym OR
Console.WriteLine(uprawnienia);                            // "Odczyt, Zapis"
Console.WriteLine(uprawnienia.HasFlag(Uprawnienia.Zapis)); // True

Atrybut [Flags] pozwala łączyć kilka wartości enum w jedną zmienną operatorem | — każda wartość musi być osobnym bitem (1, 2, 4, 8, 16…), nie kolejną liczbą (1, 2, 3, 4), inaczej kombinacje zaczną się nakładać. To standardowy sposób modelowania zestawu niezależnych opcji (uprawnienia, dni tygodnia, flagi konfiguracyjne) bez osobnej tabeli w bazie danych.

Parse i TryParse — konwersja ze stringa

string wejscie = "Wyslane";

OrderStatus status1 = Enum.Parse<OrderStatus>(wejscie);         // rzuci wyjątek, jeśli string nie pasuje
bool ok = Enum.TryParse<OrderStatus>(wejscie, out var status2); // bezpieczna wersja, zwraca bool

TryParse to bezpieczniejszy wybór, gdy string pochodzi z zewnątrz (formularz, API, plik konfiguracyjny) — nie chcesz, żeby literówka użytkownika wywaliła aplikację nieobsłużonym wyjątkiem. Domyślnie Parse/TryParse są wrażliwe na wielkość liter — "wyslane" (małą literą) nie sparsuje się bez dodatkowego parametru ignoreCase: true.

Pułapka: enum to tylko liczba — można wrzucić nieprawidłową wartość

OrderStatus status = (OrderStatus)99; // ❌ kompiluje się i działa — mimo że 99 nie istnieje w enum

Console.WriteLine(status);           // wypisze "99", nie nazwę
Console.WriteLine(status == OrderStatus.Nowe); // False, ale to wciąż "nieprawidłowa" wartość

enum w C# to w gruncie rzeczy nazwany zestaw liczb całkowitych — kompilator nie pilnuje, żeby rzutowanie z int zawsze trafiało w jedną z legalnych nazw. Jeśli wartość przychodzi z zewnątrz (baza danych, deserializacja JSON, parametr API), zawsze warto zweryfikować:

if (!Enum.IsDefined(typeof(OrderStatus), status))
    throw new ArgumentException($"Nieprawidłowy status: {(int)status}");

Pułapka: zmiana kolejności wartości psuje dane zapisane w bazie

// Wersja 1 aplikacji:
public enum OrderStatus { Nowe, Oplacone, Wyslane, Anulowane } // Wyslane = 2, zapisane w bazie jako 2

// Wersja 2 — ktoś "posprzątał" kolejność:
public enum OrderStatus { Nowe, Anulowane, Oplacone, Wyslane } // teraz 2 = Oplacone!

Jeśli zapisujesz enum do bazy danych jako liczbę (a nie string), zmiana kolejności deklaracji cicho zmienia znaczenie już zapisanych danych — rekord, który wczoraj oznaczał “Wysłane”, dziś czyta się jako “Opłacone”. To jeden z najbardziej podstępnych bugów związanych z enum, bo nic się nie wywala — dane po prostu zaczynają kłamać. Dwa bezpieczne rozwiązania: zawsze przypisuj jawne wartości liczbowe (Nowe = 0, Oplacone = 1, ...) i nigdy ich nie zmieniaj, albo zapisuj enum do bazy jako string (.ToString()), kosztem odrobiny miejsca.

Iterowanie po wszystkich wartościach

foreach (OrderStatus status in Enum.GetValues<OrderStatus>())
{
    Console.WriteLine($"{(int)status}: {status}");
}
// 0: Nowe
// 1: Oplacone
// 2: Wyslane
// 3: Anulowane

Przydatne przy generowaniu list rozwijanych w UI albo walidacji “czy ta wartość jest jedną z możliwych”. Enum.GetNames<OrderStatus>() da Ci to samo jako string[], jeśli potrzebujesz tylko nazw bez rzutowania.

Pułapka wydajnościowa: ToString() na enum jest wolniejszy niż się wydaje

for (int i = 0; i < 1_000_000; i++)
{
    var opis = status.ToString(); // w gorącej pętli to zaskakująco kosztowne
}

ToString() na enum historycznie korzystał z refleksji, co czyni go zauważalnie wolniejszym niż ToString() na zwykłej liczbie — nieistotne przy pojedynczym wywołaniu, ale realne w gorącej pętli przetwarzającej miliony rekordów (np. serializacja logów). Jeśli profiler pokaże Enum.ToString() jako źródło spowolnienia, rozwiązaniem jest ręczny switch zwracający stały string zamiast polegać na domyślnej konwersji.

Kiedy enum to za mało — smart enum

Jeśli każda wartość powinna nieść dodatkowe dane albo zachowanie (np. OrderStatus.Anulowane powinien “wiedzieć”, że nie da się z niego przejść do Wyslane), zwykły enum nie wystarczy — to sygnał do wzorca smart enum (klasa z prywatnym konstruktorem i statycznymi instancjami zamiast surowych wartości liczbowych). To już jednak temat na osobny artykuł — na start w 95% przypadków zwykły enum załatwia sprawę.

Podsumowanie

enum zamienia zbiór magicznych stringów albo liczb w nazwany, sprawdzany przez kompilator zestaw wartości — literówka nie przejdzie, IntelliSense podpowie opcje, switch ostrzeże o nieobsłużonym przypadku. Pamiętaj o dwóch pułapkach: enum to wciąż tylko liczba, więc rzutowanie nieprawidłowej wartości nie rzuci błędu samo z siebie (waliduj przez Enum.IsDefined), a zmiana kolejności deklaracji potrafi po cichu zepsuć dane już zapisane w bazie — przypisuj jawne wartości liczbowe i ich nie zmieniaj.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →