Programjaink során előfordulhat olyan eset, hogy egy változó értéke csak egy előre meghatározott elemlistából vehet fel elemet. Erre jó példa a hét napjai. Ezt tárolhatnánk egyszerű byte típuson, azonban extra kódellenőrzést kellene bevetnünk, hogy lekezeljük azt az esetet, ha véletlenül, vagy szándékosan rossz értéket adnak meg.
Az ilyen problémák kikerülésére született meg az enum. A felsorolás típus létrehozásával saját típust alkotunk. Az alábbi példakód a hét napjainak tárolására képes felsorolást hoz létre:
public enum Hetnapjai
{
Hetfo,
Kedd,
Szerda,
Csutortok,
Pentek,
Szombat,
Vasarnap
}
A példában jól látható, hogy a típust az enum kulcsszóval vezetjük be, ami után a típus neve következik. Ezután kapcsos zárójelek között vesszőkkel elválasztva a lehetséges értékek jönnek. Minden enum elem egész számok segítségével van megvalósítva. Tehát valójában az enum alapértelmezetten egy int változót jelöl, aminek a lehetséges értékeinek nevet adtunk. A felsorolás által használt adattípust megadhatjuk explicit módon is:
public enum Hetnapjai: byte
{
Hetfo,
Kedd,
Szerda,
Csutortok,
Pentek,
Szombat,
Vasarnap
}
A módosított kódban a felsorolás már egy byte típust jelöl. A típus explicit megadása esetén csak egész szám típusokból származtathatunk, vagyis a double, float és a decimal ki van zárva. A lehetséges értékeket a felsorolás nevén keresztül érhetjük el:
Hetnapjai.Hetfo
Ha float értékeket akarunk tárolni enum-hoz hasonló használati szintaktikával, akkor kreálhatunk egy statikus osztályt, amely const módosítóval ellátott double értékeket tárol:
public static class Konstansok
{
public const Pi = 3.14;
public const Sqrt2 = 1.4;
}
Az értékek explicit módon típus konvertálhatóak is:
byte ertek = (byte)Hetnapjai.Hetfo
Jelen esetben akkor van gond, ha a szám értéket szeretnénk explicit módon visszakonvertálni felsorolássá. Jelen példákban, mivel nem adtuk meg explicit módon az elemek értékeit, ezért a fordító automatikusan fog minden egyes elemhez saját értéket rendelni. Ezen érték több, mint valószínű, 0-tól kezdődik, de ebben nem lehetünk biztosak. Ezért, ha ilyen funkcionalitást szeretnénk, akkor explicit módon meg kell adni a felsorolás összes lehetőségének értékét, vagy elég csak az első értékét megadni.
Ha csak az első értéket adjuk meg, akkor a fordító úgy fogja elkészíteni a kódot, hogy az első elem után megadott értéke eggyel nagyobb lesz, mint a kezdő elemé. Tehát az alábbi módosított példában a Kedd értéke kettő lesz:
public enum Hetnapjai: byte
{
Hetfo = 1,
Kedd,
Szerda,
Csutortok,
Pentek,
Szombat,
Vasarnap
}
Megadhatunk bármelyik elemnek konkrét számértéket és nem kötelező ezt növekményesen megtenni. Viszont ha ezt a megoldást választjuk, akkor a felsorolás minden elemének adjunk értéket az egyértelműség kedvéért:
public enum Hetnapjai: byte
{
Hetfo = 112,
Kedd = 4,
Szerda = 8,
Csutortok = 31,
Pentek = 42,
Szombat = 11,
Vasarnap = 18
}
Mivel az enum egy egész számot jelöl, ezért minden gond nélkül használható switch-case szerkezetben is. Itt megjegyezném, hogy a felsorolásainkban hiba kezelés miatt a default ágnak érdemes lehet bevezetni egy alapértelmezett értéket, ha leírni kívánt adat ezt megengedi.
Felsorolások esetén különösen hasznos az Enum osztály Parse és TryParse metódusa, melyek segítségével szövegek könnyen konvertálhatóak felsorolás értékekké. (A visszatérési érték object, amit konvertálni kell majd a tényleges enum típusra.)
object Enum.Parse(Type enumType, string value);
object Enum.Parse(Type enumType, string value, bool ignoreCase);
A Parse metódus első paramétere a konvertálandó enum típusa. A második paramétere pedig a konvertálandó szöveg. A metódus visszatérése egy generikus enum, amit át kell még alakítanunk a cél típus értékre. A metódus nem kezeli a kivételeket és ArgumentException típusú kivételt dob abban az esetben, ha a szöveg által meghatározott név nem része a felsorolásnak.
A három paraméteres változatában, amennyiben a harmadik paraméter igaz értékű, akkor konverzió esetén a kis- és nagybetűs elnevezések nem számítanak.
bool Enum.TryParse(string value, out TEnum result) where TEnum : struct;
bool Enum.TryParse(string value, bool ignoreCase, out TEnum result) where TEnum : struct;
Generikus feldolgozó variáns, amely kivételt is kezel. Amennyiben a konvertálás sikerült, akkor a függvény visszatérési értéke igaz. A <> zárójelek között a kimeneti enum típusát kell meghatározni. A metódus első paramétere a konvertálandó szöveget határozza meg, a második paraméter pedig a kimeneti változót. A Parse metódushoz hasonlóan itt is lehetőség van bekapcsolni, hogy a kis- és nagybetűs elnevezések ne számítsanak konverzió esetén.
Az alábbi példa ezen metódusok használatát mutatja be:
using System;
namespace PeldaEnum
{
class Program
{
enum Hetnapjai : byte
{
Hetfo = 1,
Kedd,
Szerda,
Csutortok,
Pentek,
Szombat,
Vasarnap
}
static void Main(string[] args)
{
string[] tesztek = { "1", "7", "0", "99", "Hetfo", "Szerda", "Pentek", "pentek", "Hiba" };
foreach (var teszt in tesztek)
{
try
{
Hetnapjai nap = (Hetnapjai)Enum.Parse(typeof(Hetnapjai), teszt);
Console.WriteLine(nap);
}
catch (ArgumentException ex)
{
Console.WriteLine("Hiba: " + ex.Message);
}
Hetnapjai nap2 = Hetnapjai.Hetfo; //alap érték
Enum.TryParse(teszt, out nap2); //generikus tryparse hívás
Console.WriteLine(nap2);
}
Console.ReadKey();
}
}
}
A program kimenete:
Hetfo
Hetfo
Vasarnap
Vasarnap
0
0
99
99
Hetfo
Hetfo
Szerda
Szerda
Pentek
Pentek
Hiba: Requested value 'pentek' was not found.
0
Hiba: Requested value 'Hiba' was not found.
0
A példa kimenetében jól látható, hogy az olyan számok esetén, mint a 0 vagy a 99, mindkét konverziós metódus a numerikus 0 vagy 99-et fogja visszaadni.
A „pentek” és „Hiba” bemenetek esetén a Parse hibát dob, a TryParse kimenete pedig 0 lesz, mivel az általános, numerikus értékkel nem meghatározott felsorolások esetén is a számozás nulláról indul. Ha a Hetfo értéke nulla lenne, akkor mindkét hibás bemenet esetén a Hetfo szöveg jelenne meg.
Flag felsorolások
A flag szó angolul zászlót jelent, programozásban azonban a jelölő szó használata kézenfekvőbb. A flag felsorolások olyan felsorolások, amelyek értékei kombinálhatóak. Ez a viselkedés hasonlít a CPU belső flag regiszterére, ahol egyszerre több bit is igaz értékű lehet.
A flag felsorolások például fájl attribútumok jelzésére hasznosak, vagy akkor, ha több igaz, hamis értéket szeretnénk kompakt helyen tárolni. A flag felsorolás szintaktikája a következő:
[Flags]
enum FlagPelda
{
None = 0,
Egyik = 1,
Masik = 2,
Harmadik = 4
}
A módosulás a szintaktikában a [Flags] jelölő attribútum1, ami a többi fejlesztőnek egy jelzés, hogy az enum értékei kettő hatványai szerint kapnak értéket. Vagyis a példában az Egyik értéke kettő nulladik hatványa értékű (1) lesz, a Másik kettő első hatványa lesz (2), és így tovább.
Egy összetett enum értéket a bináris vagy ( | ) operátor segítségével tudunk létrehozni:
var pedla = FlagPelda.Egyik | FlagPelda.Masik;
Lekérdezni, illetve tesztelni egy specifikus flag értékre pedig a bináris és ( & ) operátor segítségével tudunk:
if ((pedla & FlagPelda.Egyik) == FlagPelda.Egyik)
{
//kód ha a flag igaz értékű
}
A Flags annotáció jelzésen kívül a ToString() működését befolyásolja még. Az ilyen módon jelölt enumok esetén minden beállított érték kiíródik. A korábbi pelda változón ha meghívjuk a `ToString()„ metódust, akkor a következő szöveget kapnánk eredményül:
Egyik, Masik
A kettő hatványait értékül felvevő enum típusok esetén a nulla értékről sem szabad elfelejtkezni, ami indikálja, hogy a lehetséges értékek közül egy sem került beállításra. Kódolási ajánlás, hogy ennek a neve None legyen.
Fontos megjegyezni, hogy explicit típus konverzióval egész számok enum típussá konvertálhatóak, azonban ha a szám érték nem szerepel a felsorolás értékében, akkor nem kapunk kivételt a program futása közben, illetve fordítási hibát sem:
FlagPelda p = (FlagPelda)99;
A fenti kódrészlet működő C# kód, ami hibás működést fog eredményezni a programunkban, ahol a FlagPelda típusra és értékeire számítunk.
Éppen ezért enum típusok értékeinek vizsgálatakor készüljünk fel arra az esetre, amikor olyan érték érkezik, ami nem szerepel az engedélyezett esetek között. Valamint ha a felhasználótól bekért szám alapján állítunk vissza egy enum értéket, akkor mindig ellenőrizzük a bekérés helyén, hogy az a szám szerepel-e az enum által felvehető értékek között.
-
Ezek használatáról részletesen a reflection kapcsán lesz szó.↩