Bezpieczne typy wyliczeniowe (type-safe enum) w C#

.NET, C#, Wzorce projektowe

Typ wyliczeniowy „enum” znany jest z całą pewnością każdemu adeptowi sztuki programowania (począwszy od starych wyg, a skończywszy na studentach pierwszego roku lub samoukach drążących podręczniki programowania). Jego użycie wydaje się banalne, stosowanie go niesie jednak za sobą pewne zagrożenia. Jakie? O tym przeczytacie poniżej.
Jakiś czas temu w jednym z programów odziedziczonych po poprzednich twórcach napotkałem następujący fragment kodu:

public enum Status
{
   New = 1,
   OnHold,
   Pending,
   Closed,
   Canceled
}

public void SetStatus(Status status)
{
   //Do something...
}

Wszystko wydawało się działać poprawnie, dopóki nie wydarzyła się tragedia. Tragedia następującej treści:

SetStatus((Status)7);

Co się stało? Najwyraźniej ktoś zmodyfikował działającą wcześniej deklarację typu „Status”, natomiast zapomniał o przejrzeniu kodu wykorzystującego zadeklarowany, omawiany typ wyliczeniowy. Jaki będzie efekt działania powyższej linii kodu wszyscy potrafimy sobie wyobrazić (każdy kto ma w tym momencie przed oczami wyrzyganą na ekran treść wyjątku – niech lepiej zapozna się z tą dyskusją).

Co możemy zaradzić na powyższą sytuację? Rozwiązaniem w pewnych przypadkach może być zastosowanie wzorca „Type-safe enum”. Przykładowy kod znajdziecie poniżej:

public sealed class Status
{
   public static readonly Status New = new Status(1, "New");
   public static readonly Status OnHold = new Status(2, "OnHold");
   public static readonly Status Pending = new Status(3, "Pending");
   public static readonly Status Closed = new Status(4, "Closed");
   public static readonly Status Canceled = new Status(5, "Canceled");

   public readonly int Id;
   public readonly string Name;

   private Status(int id, string name)
   {
      this.Id = id;
      this.Name = name;
   }
}

Powyższa deklaracja gwarantuje prawie identyczną funkcjonalność, co systemowy typ wyliczeniowy. Zaletą jej zastosowania jest to, że przytoczony wcześniej kod wywołujący funkcję SetStatus(Status) zwróci błąd już na etapie kompilacji, a nie uruchomienia.
Wady bezpiecznego typu wyliczeniowego:

1. Jest to konstrukcja „cięższa” niż standardowy „enum”. W specyficznych przypadkach (np. przy serializacji/deserializacji danych) jej obsługa wymaga większego nakładu pracy.

2. Dostarcza nam zmienną typu „nullable”, (podobnie jak zresztą domyślny typ wyliczeniowy). Niektórzy rozwiązują ten problem stosując do implementacji wzorca strukturę, zamiast klasy zapieczętowanej.

3. W przeciwieństwie do standardowego typu wyliczeniowego, nie może być wykorzystany wewnątrz konstrukcji „switch” (o tym jak sobie z tym poradzić przeczytacie z kolei tutaj )

Total Views: 465 ,