arckép

Neuwirth István

programtervező informatikus MSc – ELTE

Computational and Software Techniques MSc – Cranfield, UK

Elérhetőség
pitta2@gmail.com
+36 30 329 3039

Valid XHTML 1.0 Transitional

Valid CSS!

C# – a Flags attribútum

A felsorolási típusról bővebben

Egy felsorolási típus megadásakor gyakorlatilag a típus típusértékhalmazát adjuk meg valamilyen felsorolás szerint. Sok nyelv esetén ez csupán valamilyen konstans értékekre való névvel hivatkozást jelent, az adott felsorolási típusú változó a típusértékhalmaz valamely értékét kaphatja értékül. A C# nyelvben azonban lehetőségünk van egy ilyen változóhoz az értékek közül akár egyszerre többet is hozzárendelni, flag-ekként használva őket.

C# programkódban egy felsorolási típus létrehozása megegyezik a C++ alatt megszokottal, azaz alapesetben enum Nev { Ertek1, Ertek2, Ertek3 }; vagy enum Nev { Ertek1 = 2, Ertek2 = 10, Ertek3 = 8 }; alakban adható meg. Itt az egyes konkrét értékek a példának megfelelően egész számok lehetnek, mégpedig olyan egész számok, amelyek a felsorolási típus alaptípusának értékkészletéből kerülnek ki. Az alaptípus – ha mást nem adunk meg – int lesz, ami a .Net Int32-es típusának másodneve. Alaptípusnak bármely egész számot reprezentáló típus használható, azaz sbyte, byte, short, ushort, int, uint, long, ulong egyaránt, a megadás módja pedig a következő: enum Nev : byte { Ertek1, … };. Ahogy a példában is látszik, az alaptípus byte lett, így az ilyen típusú változók tárolásakor némi memóriát vagy tárterületet spórolhatunk, ami jól jöhet hálózati forgalom optimalizálásánál vagy mobil eszközökre való fejlesztésnél (lásd még .Net Compact Framework). Ha külön nem adunk meg értékeket, akkor a rendszer automatikusan számoz, nullától kezdve egyesével.

Tegyünk egy kis kitérőt a flag-ekkel kapcsolatban! Ha van néhány igaz/hamis tulajdonságom, akkor azokat logikai típusok helyett célszerű lehet egy bitsorozat bitjeiként ábrázolni, így jelentős memóriát vagy tárterületet megspórolni. Tehát egy flag egy jelzőbit lesz, 0 hamis esetben, 1 igaz esetben. Egy nyolc bitből álló oktet így nyolc tulajdonságot írhat le egy bájton, bool típusú változók használatakor pedig ugyanez a nyolc tulajdonság nyolc bájtot foglalna el (sizeof(bool) == 1). Bináris reprezentációt kézzel állítgatni azonban nem túl kényelmes, se nem túl elegáns, de szerencsére a felsorolási típus tud nekünk segítséget nyújtani, a Flags attribútum használatával.

A fentiek használatához a felsorolási típusunkat adjuk meg a  következőképp: legyen egy 0 értékű mezőnk is, ha egyik tulajdonság sem teljesülne, a többi mezőnek értékül pedig egy-egy kettőhatványt adjunk. A Flags attribútum használata ezenkívül összesen annyival jár, hogy a típusunk elé egy [FlagsAttribute], vagy rövidebben [Flags] sort írunk. Az attribútum hatása az lesz, hogy az egyes értékek között a bitenkénti (bitwise) operátorokat használhatjuk, így a bitenkénti ÉS (&), bitenkénti VAGY (|), bitenkénti KIZÁRÓ VAGY (^) vagy a bitenkénti NEGÁLÁS (~) műveleteket hajthatjuk végre. Mi ezek közül a bitenkénti ÉS, VAGY és NEGÁLÁS műveleteket fogjuk használni. A példát figyelembe véve a Kutya k = Kutya.Szelid; programsor hatására k bináris reprezentációja 00000010, a Kutya k = Kutya.Ugat; programsor hatására pedig 00000001 lesz. Ez triviális, ugyanis csupán a megfelelő számértékek bináris alakját kapjuk vissza. Ha több értéket szeretnénk egyszerre használni, akkor a következő példa szerint járhatunk el: Kutya k = Kutya.Szelid | Kutya.Ugat;.  A bináris reprezentációk szintjén tehát 00000010 | 00000001, ami pedig egyenlő 00000011-gyel, amin látszik, hogy a két legkisebb helyiértéken szereplő flag „bekapcsolt” állapotban található.  Egy már adott változóhoz értéket hozzávenni rövidebben a |= operátorral (k |= Kutya.Jatekos;) tudunk, azaz így tudunk egy flag-et „bekapcsolt” állapotba állítani. Egy flag-et „kikapcsolni” a következő példa szerint tudunk: k &= ~Kutya.Szelid;. Ha le szeretnénk kérdezni, hogy egy flag „bekapcsolt” állapotban van-e, a következő alakú logikai kifejezést használjuk feltételként: (k & Kutya.Szelid) == Kutya.Szelid. A bináris reprezentációk felírásával, illetve a bitenkénti operátorok művelettáblájának figyelembevételével a fenti kifejezés működése egyértelmű. A zárójelezésre szükségünk van, ugyanis a == operátor precedenciája nagyobb, mint az & operátoré, és egy felsorolási típus és egy bool típus között az & operátor nincs értelmezve, tehát a fordító a zárójelek elhagyásakor hibát ad.

Megemlíthető még a felsorolási típusok kapcsán a string reprezentációjuk előállítása, illetve egy már létező string-ből a változó értékének beállítása. Egy felsorolási típusú változó string alakját egyszerűen a változó ToString() metódusának meghívásával kaphatjuk, ami a .Net-es Object osztály ToString() metódusának felülírt változata. Megfigyelhető, hogy amennyiben egy változóban több flag is bekapcsolt állapotban található, akkor az egyes flag-ek neveit a string-ben egymás után, vesszővel elválasztva kapjuk meg. Ha egy string-ből szeretnénk előállítani a változót, akkor a következő alakot használhatjuk: Kutya k = (Kutya)Enum.Parse(typeof(Kutya), "Szelid, Ugat");.

A további lehetőségekhez érdemes megvizsgálni az Enum osztály (System névtér) statikus metódusait, segítségükkel az egyes értékeket, azok neveit (GetValues, GetName és GetNames metódusok) és az alaptípust is lekérdezhetjük (GetUnderlyingType metódus).

[Flags]
enum Kutya : byte
{
    Semmilyen = 0,
    Ugat = 1,
    Szelid = 2,
    Jatekos = 4,
    Gyors = 8
}

...
Kutya k1 = Kutya.Jatekos;
Kutya k2 = Kutya.Szelid | Kutya.Gyors;
...