.NET Enumeration Types

Nabrojivi tipovi ili Enumeration Types su jedan od osnovnih pojmova u .NET jezicima. Iako se relativno često koriste, dešava se da previdimo neke njihove mogućnosti. Rešio sam da malo detaljnije ispitam šta sve može da nam ponudi ovaj poznati koncept.

Enumi su tipovi podataka koji nam omogućavaju da, praktično, dodelimo nazive skupu celobrojnih vrednosti (preciznije vrednostima integralnog tipa). Cilj je brži i jednostavniji razvoj i kasnije održavanje, odnosno čitljivost i jasnoća koda koju dobijamo korišćenjem enum tipova umesto konstanti. Korišćenjem enuma definisali smo listu dozvoljenih vrednosti, dobili IntelliSense podršku i još neke prednosti u odnosu na korišćenje numeričkih konstanti.

Složićete se da je mnogo čitljivije, ukoliko u kodu umesto vrednosti 2 stoji:

FileMode.Create

Uz pomoć xml komentara možemo da dobijemo i ovako nešto:

FileMode Enum - FileMode.Create

Da bi definisali nabrojivi tip koristimo ključnu reč enum (C# Reference). Bazna klasa za enume je System.Enum, koja sadrži korisne metode za rad sa njima, neke ćemo koristiti u primerima. Enumi su value type, znači da nema nasleđivanja i dodela kopira vrednost jednog enuma u drugi.

Evo primera:

   enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

Podrazumevano vrednosti enuma su tipa int, moguće je eksplicitno izabrati tip vrednosti:

   enum Months : byte { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };

Podrazumevano, vrednosti se automatski dodeljuju tako što se uvečavaju za 1, znači u prvom primeru Days.Sunday ima vrednost 0, Days.Monday 1… Naravno moguće je i eksplicitno odrediti vrednosti, jednu ili više njih, u narednom primeru Days.Sunday imaće vrednost 1, Days.Mondey 2…

enum Days
{
   Sunday = 1,
   Monday,
   Tuesday,
   Wednesday,
   Thursday,
   Friday,
   Saturday
}

Šta će kao izlaz dati sledeći kod?

   Days day = Days.Friday;
   Console.WriteLine("{0} {1}", day, (int)day);

Ovo je lako: Friday 6

Ako želimo da dobijemo naziv vrednosti našeg enuma možemo da koristimo jednu od ove dve linije koda, koje vraćaju istu vrednost:

   ((Days)2).ToString()
   Enum.GetName(typeof(Days), 2)

Često nam može zatrebati da prođemo kroz sve vrednosti enuma, klasa Enum nam nudi dve korisne metode GetValues i GetNames. Već po nazivima je jasno šta rade GetValues – vraća niz svih vrednosti enuma, a GetNames vraća niz njihovih naziva. Evo primera:

   foreach (string str in Enum.GetNames(typeof(Days)))
      Console.WriteLine(str);

Ako imamo naziv, a želimo da dobijemo vrednost? Koristimo Enum.Parse (ili TryParse):

   Days day = (Days)Enum.Parse(typeof(Days), "Monday");

Poželjno je da pre konverzije stringa u enum proverimo da li je naziv pripada našem enumu, npr:

  string str = "MyDay";
  Days day;

  if (Enum.IsDefined(typeof(Days), str))
     day = (Days)Enum.Parse(typeof(Days), str);
  else
     Console.WriteLine("Not defined");

Flags

Uobičajeno je da se enumi koriste za međusobno isključive vrednosti, znači dan je ili poneljak ili utorak, ne može biti i jedno i drugo. Nekada želimo da vrednost enum objekta sadrži više članova, na primer želimo da imamo vrednost Weekend koja sadrži dva dana: subotu i nedelju, u ovakvim slučajevima koristimo FlagsAttribute.

Napomenuo sam da su u osnovi enuma celobrojne vrednosti, što nam omogućava da nad njima koristimo operatore koji rade nad bitovima (bitwise operators). Za početak da dodamo Flags atribut:


[Flags]
enum SuperDays : byte
{
   None = 0x0,        // 0000 0000
   Sunday = 0x1,      // 0000 0001
   Monday = 0x2,      // 0000 0010
   Tuesday = 0x4,     // 0000 0100
   Wednesday = 0x8,   // 0001 0000
   Thursday = 0x10,   // 0010 0000
   Friday = 0x20,     // 0100 0000
   Saturday = 0x40,   // 1000 0000
   WorkingDays = Monday | Tuesday | Wednesday | Thursday | Friday
}

Koristio sam hexadecimalnu notaciju za vrednosti, kao komentar je data binarni zapis da bi bilo jasnije. Poenta je da su vrednosti našeg enuma SuperDays bajtovi u kojima je tačno jedan jedan bit na jedinici, tj. stepeni dvojke, što nam omogućava neke lepe trikove. Npr. ako želimo da zakažemo sastanak koji se održava svake nedelje ponedeljkom i sredom, možemo da našoj promenljivoj dodelimo vrednost:

   SuperDays meetingDays = SuperDays.Monday | SuperDays.Wednesday;

Sada meetingDays sadrži vrednosti oba dana. Što možemo da <em>dokažemo</em>, linija koda:

   Console.WriteLine(meetingDays);

Daje izlaz: Monday, Wednesday

Zanimljiv je i operator ^ (isključivo ili) koje će, u našem primeru, dodati dan ukoliko već nije sadržan u promenljivoj meetingDays, a ukoliko je već unutra izbaciće ga. Na primer, posle ove linije petak je u našoj promenljivoj:

   meetingDays ^= SuperDays.Friday;

a ukoliko je ponovimo još jednom petak će biti izbačen, zgodno zar ne?

Analogno možemo da (zlo)upotrebimo i ostale operatore. Pa tako kod SuperDays.Wednesday & meetingDays vraća sredu (SuperDays.Wednesday), a ~SuperDays.Monday sve dane osim ponedeljka.

Od verzije 4.0 .NET frejmvorka, imamo i metodu HasFlag, tako da možemo da proverimo da li je vrednost setovana:

   meetingDays.HasFlag(SuperDays.Monday)

naravno, ako ustreba tu su i bit operatori:

   (meetingDays & SuperDays.Monday) != 0

Description

Nazivi enum vrednosti nisu uvek najbolje rešenje za prikazivanje na korisničkom interfejsu, zbog ograničenja koje nameće definicija jezika. Najbanalniji primer je to što ne možemo da koristitimo blank karakter. U pomoć zovemo DescriptionAttribute i extension metode.

Definišemo naš enum:

enum Gender
{
   [Description("Male")]
   M,
   [Description("Female")]
   F,
   [Description("Unisex")]
   U
}

Evo i klase koja sadrži extension metode, komentarisanje samih metoda bi oduzelo previše prostora, pa ću samo pokazati kako ih možete koristiti. Imajte u vidu da postoji i kompletnije rešenje koje omogućava lokalazaciju pomoću resource stringova, lep primer možete naći na stackoverflow.

static class EnumExtensionMethods
{
   public static string ToDescription(this Enum en)
   {
      Type type = en.GetType();
      MemberInfo[] memInfo = type.GetMember(en.ToString());

      if (memInfo != null && memInfo.Length > 0)
      {
         object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
         if (attrs != null && attrs.Length > 0)
            return ((DescriptionAttribute)attrs[0]).Description;
      }
      return en.ToString();
   }

   public static T? ToEnum<T>(this string s) where T : struct, IConvertible
   {
      return (T?)Enum.Parse(typeof(T), s);
   }

   public static T? ToEnumFromDescription<T>(this string description) where T : struct, IConvertible //where T : enum
   {
      foreach (FieldInfo fi in typeof(T).GetFields())
      {
         object[] attrs = fi.GetCustomAttributes(typeof(DescriptionAttribute), true);
         if (attrs != null && attrs.Length > 0)
         {
            foreach (DescriptionAttribute attr in attrs)
            {
                if (attr.Description.Equals(description))
                   return (T)fi.GetValue(null);
            }
        }
      }
      return default(T);
   }
}

Sada možemo da dobijemo opis vrednosti enuma iz atributa, kao i da dobijemo vrednost enuma na osnovu opisa:

   Gender gender = Gender.M;
   Console.WriteLine(gender.ToDescription());
   // output -> male

   Console.WriteLine("Male".ToEnumFromDescription<Gender>());
   // output -> M

Toliko za danas, u sledećem postu poradićemo i na korisničkom interfejsu, tj. načinu na koji se može bajndovati na enum vrednost.

Pages:
  1. Odličan post. Drago mi je da vidim sve više IT blogova na srpskom.

    • Branimir

      Hvala Ivane. Nadam se da će biti još više IT blogera usmerenih ka lokalnoj zajednici, radimo na tome :)

  2. Nemanja

    Odlican post.

  3. A reci mi, ako se radi o dobu dana, da li u enum-u mogu da definisem opseg za, recimo, Noc od 0-5, i kad ukucam broj 3, tj. 3h, da mi istampa string Noc? Jel moguce definisati opsege u enum-u uopste, ili to radim negde drugde?

  4. Hm, zanimljivo pitanje. Enumi, u ovom slučaju, mogu da posluže da definišeš svoja doba dana, npr:

    enum DobaDana
    {
       Noc,
       Dan
    }

    Što se tiče mapiranja sata u ova doba, predlažem da iskoristiš Dictionary (ili HashTable), npr. Dictionary<int, DobaDana>, gde bi ključ bio sat.

    Dictionary<int, DobaDana> sati = new Dictionary<int, DobaDana>()
    {
       {1, DobaDana.Noc},
       {2, DobaDana.Noc},
       …
       {8, DobaDana.Dan}
       …
    };

    Kada inicijalizuješ ovaj dictionary, onda jednostavno „pitaš“ svoj dictionary koje je doba dana za izabrani sat:

    sati[2] //vraća Noc
    sati[8] //vraća Dan

    Kako ti se ovo čini?

Leave a Comment

NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>