Jak dodać opis wartości ENUM?

Czasami typ ENUM znajduje zastosowanie(czasami ponieważ często ogranicza on modułowość aplikacji). W wielu przypadkach potrzebujemy jednak skojarzyć pewien opis z każdą wartością enum’a. Jako praktyczny scenariusz można wymienić implementację menedżera dźwięków. Dla przykładu w pewnej grze, którą współtworzyłem aby uatrakcyjnić interfejs dla programisty zdefiniowałem sobie typ enumeryczny SOUND_TYPE:

public enum SOUND_TYPE
{
   ROCKET_LAUNCH,
   MACHINE_GUN_LAUNCH
}

Programista zatem chcąc wykorzystać dźwięk przedstawiający strzał rakiety wywołuje następującą metodę:

SoundManager.Instance.PlaySoundEffect(SOUND_TYPE.ROCKET_LAUNCH);

Dla użytkownika kodu wprowadzenie typu ENUM jest bardzo korzystne ponieważ wywołując metodę PlaySoundEffect nie da się wprowadzić błędnego parametru wejściowego. Powyższa definicja ENUM nie definiuje jednak w żaden sposób ścieżek plików dźwiękowych. W tym celu stworzyłem atrybut przypisujący każdemu typowi ENUM ścieżkę prowadzącą do pliku z dźwiękiem:

public enum SOUND_TYPE
{
   [AssetName(AssetName = "rocket_gun_launch")]
   ROCKET_LAUNCH,
   [AssetName(AssetName = "machine_gun_launch")]
   MACHINE_GUN_LAUNCH
}

Deklaracja AssetName to:

public class AssetNameAttribute:Attribute
{
   public string AssetName
   {
       get;
       set;
   }        
}

Mamy więc zdefiniowany typ ENUM oraz przypisaną ścieżkę pliku. Pozostaje nam tylko dostać się do tych danych. W tym celu zdefiniowałem metodę rozszerzającą(extensions, c# 3.0):

public static class EnumEx
{
   static public attr GetAttributeValue<attr>(this Enum primaryEnum) where attr:class
   {
       FieldInfo field = primaryEnum.GetType().GetField(primaryEnum.ToString());
       Attribute[] attrs = field.GetCustomAttributes(typeof(attr), false) as Attribute[];
       if (attrs.Length == 0) return null;
       else
           return attrs[0] as attr;
   }
}

Teraz aby odczytać ścieżkę do pliku dla podanego enum’a wystarczy:

SOUND_TYPE soundType = SOUND_TYPE.ROCKET_LAUNCH;
string filePath = soundType.GetAttributeValue<AssetNameAttribute>().AssetName;

Scenariuszy jest naprawdę wiele na wykorzystanie powyższego rozwiązania. Dla przykładu wyobraźmy sobie, że implementujemy CRM i mamy listę telefonów. W ENUM możemy zdefiniować sobie typy HOME, WORK  itp. Następnie za pomocą własnego atrybutu możemy dodać opis każdej kategorii, który będzie wyświetlany w interfejsie. Za pomocą atrybutów możemy także definiować reguły walidacyjne dla zmiennych(RangeAttribute, RequiredAttribute, DefaultAttribute itp).

Na koniec pozostaje mi wyjaśnienie 2 kwestii.

Po pierwsze zawsze powinniśmy zastanowić się czy aby na pewno warto wprowadzać typ ENUM do budowanego systemu. Wracając do przykładu CRM, co jeśli pewnego dnia okaże się, że wymagana jest trzecia grupa telefonów np. MOBILE? Wprowadzanie jej wymagało by modyfikacji kodu co w aplikacjach klasy enterprise jest niedopuszczalne. Najlepiej podsumowuje ten wniosek, jedno z podstawowych zasad w inżynierii oprogramowania Open\Closed Principle:

A module should be open for extension but closed for modification”

Druga to wytłumaczenie się dlaczego zdecydowałem się użyć SOUND_TYPE w grze skoro tak neguje większość przypadków użycia ENUM. Wynika to po prostu z przyjętych wymagań, technologii oraz czasu na wykonanie gry. Gra została napisana w XNA, która wykorzystuje tzw. Content Pipiline. Nie wchodząc w szczegóły wszelkie zasoby gry(dźwięki, modele, efekty) muszą być przekompilowane i zamienione na specjalny format obsługiwany zarówno na PC jak i XBOX. Skoro zasoby nie mogą być dodawane dynamicznie przez użytkowników(ponieważ muszą być rekompilowane) to po co męczyć się z rozszerzalnym interfejsem, który i tak nigdy nie zostanie wykorzystany?

3 thoughts on “Jak dodać opis wartości ENUM?”

  1. Ciekawy wpis 😉

    Jak wspomniałeś, w CRM używa się sprawdzonych sposobów, czyli przy uruchomieniu aplikacji słowniki z bazy zostają załadowane i na nich się pracuje cały czas.

    Czasami się zastanawiałem, jak ciężki w utrzymaniu byłby taki sposób. Zbudować jedną bibliotekę, która zostaje kompilowana przy uruchomieniu programu,a w niej, za pomocą np. t4 ze słowników w bazie są tworzone enumy – swoją drogą mógłby wyjść niezły hardcore. Z punktu widzenia osoby użytkującej zmiana nie byłaby widoczna, w razie jakiejkolwiek modyfikacji potrzebny jest restart aplikacji, w przypadku korzystania ze słowników z bazy jest tak samo, bo nie znam nikogo kto by ich nie cachował 😉 Jednak pod względem utrzymania kodu mógłby być kłopot.

  2. Moim zdaniem lepszym rozwiązaniem jest implementacja enumów Java-Like.
    public sealed class JavaLikeSoundType {
    public static readonly JavaLikeSoundType RocketLaunch = new JavaLikeSoundType(“rocket_gun_launch”);
    public static readonly JavaLikeSoundType MachineGunLaunch = new JavaLikeSoundType(“machine_gun_launch”);
    public String Path { get; private set; }
    private JavaLikeSoundType(string path)
    {
    this.Path = path;
    }
    }

    Pozwala rozszerzać klasę o metody i właściwości bez stosowania refleksji i z silniejszą kontrolą typów.
    Zamiast pól readonly można użyć statycznych właściwości z prywatnymi seterami. Dodatkowo takie “Enumy” mogą implementować interfejsy (np. grupować standardowe bezstanowe implementacje interfejsu).
    Podobnie klasa nie musi być sealed i może mieć wewnętrzne prywatne klasy dziedziczące.

  3. Notka bardziej opisuje wykorzystanie atrybutów niż Enum’a;)

    @wojtek(szogun1987): Dobry sposób – podoba mnie się. Człowiek uczy się przez całe krótkie życie.

Leave a Reply

Your email address will not be published.