Szél Péter

Az előző bejegyzésemben írtam arról, hogy C#-ban egy enum típusú változó tetszőleges értéket felvehet attól függetlenül, hogy az az érték definiálva van-e az adott Enumban, vagy nincs. Ezt a tulajdonságát fogjuk most az előnyünkre fordítani.

A megoldandó feladat

Vegyük az előző blog-bejegyzésemben használt példát:

enum Permission  
{
    Read = 1,
    Write = 2,
    Execute = 3
}

Ha ez egy file jogosultság, akkor jó eséllyel egy file-hoz több érték is megadható egyszerre, hiszen egy adott állomány egyidejűleg lehet írható, olvasható vagy akár végrehajtható is.

A feladatot könnyedén meglehetne oldani egy Permission listával, amely tartalmazza az adott fájlhoz beállított jogosultságokat, de hadd mutassak egy elegánsabb megoldást! :)

A Bit-jelölő megoldás

Számozzuk át az enumot, jelentse egy-egy bit a különböző értékeket:

enum Permission  
{
    Read = 1 << 1,
    Write = 1 << 2,
    Execute = 1 << 3
}

Ebben a példában az első bit jelenti a Read jogot, a második a Write jogot és a harmadik az Execute jogot.
(Persze 1 << 2 helyett nyugodtan írhattam volta 4-et is, viszont ebben a formában jobban kifejezi a szándékomat, miszerint az adott bit hordozza az információt.)

Így ha egyszerre akarunk beállítani Read és Write jogot is, akkor nagyon egyszerűen és olvashatóan megtehetjük:

Permission permission = Permission.Read | Permission.Write;  

Ennek megfelelően így nézne ki a File osztályunk:

class File  
{
    Permission permissions;

    public void GrantPermission(Permission permission)
    {
        permissions |= permission;
    }

    public bool HasPermission(Permission permission)
    {
        return (permissions & permission) == permission;
    }
}

Az így alkotott File osztály jóval hatékonyabb minden szempontból, hiszen a jogosultságok tárolását egyetlen 32 bites változóban tárolja, valamint az egyes műveletek is a lehető legegyszerűbb – és leggyorsabb – bitműveletek.

Ha meg akarjuk védeni a File osztályunkat a helytelen használattól, akkor a GrantPermission metódusba érdemes egy Enum.IsDefined hívást beiktatni a beállítás elé, ez azonban sajnos már dobozolással jár, ami csökkenti a hatékonyságot.

A HasPermission megvalósítása így még nem igazán nevezhető könnyen olvashatónak, szerencsére a .NET framework erre is szolgál megoldással:

A HasFlag() metódus

Kapunk ajándékba egy metódust az enumunkhoz, amellyel könnyedén és kényelmesen vizsgálhatunk az adott flagre:

Permission permission = Permission.Read | Permission.Write;

if(permission.HasFlag(Permission.Read))  
{
    // Read the file
}

Ennek köszönhetően a HasPermission implementációnk is jóval olvashatóbbá válik:

public bool HasPermission(Permission permission)  
{
    return permissions.HasFlag(Permission.Read);
}

Vigyázat: a megoldás hátránya, hogy a változónk így dobozolásra kerül, csökkentve a hatékonyságot.

Flags attribútum

A .NET keretrendszer további segítséget is nyújt az Enum fentihez hasonló használatához a Flags attribútum segítségével. Ha kidekoráljuk vele az Enumunkat:

[Flags]
enum Permission  
{
    Read = 1 << 1,
    Write = 1 << 2,
    Execute = 1 << 3
}

akkor az alábbi nagyszerű lehetőség is rendelkezésre áll.

ToString() implementáció

Az Enum ToString() implementációja figyelembe veszi a Flags attribútumot, vesszővel elválasztott lista lesz az eredmény ilyenkor:

Permission permission = Permission.Read | Permission.Write;  
Console.WriteLine(permission);

>> Read, Write

Tetszett az írásom? Akkor oszd meg az alábbi gombok használatával! :)