Flagi w Enum

Typy Enum są ładnym rozwiązaniem służącym do definiowania stałych. Zamiast przekazywać nic nie mówiącą liczbę do funkcji, możemy zdefiniować enum i przekazać np. TextAlignment.Left – z pewnością poprawi to czytelność kodu. Czasami jednak chcemy przekazać dwa enumy naraz lub dowolną ich kombinację. W tym celu musimy skorzystać z tzw. flag. Zacznijmy od razu od przykładu:

[Flags]
enum Days
{
    None = 0,
    Sunday = 1,
    Monday = 2,
    Tuesday = 4,
    Wednesday = 8,
    Thursday = 16,
    Friday = 32,
    Saturday = 64
}

Pierwszą kwestią jest atrybut Flags. Jak widać określiłem z góry wartości dla poszczególnych enum – jest to niezbędne, jednak  wyjaśnię to dopiero na końcu post’a. Najpierw zobaczmy co dzięki flagom możemy uzyskać (źródło MSDN):

// Initialize with two flags using bitwise OR.
Days meetingDays = Days.Tuesday | Days.Thursday;

// Set an additional flag using bitwise OR.
meetingDays = meetingDays | Days.Friday;

Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Tuesday, Thursday, Friday

// Remove a flag using bitwise XOR.
meetingDays = meetingDays ^ Days.Tuesday;
Console.WriteLine("Meeting days are {0}", meetingDays);

Operator OR (w C# |) służy do tworzenia kombinacji z poszczególnych typów. Z kolei XOR (W C# ^) odpowiedzialny jest za usuwanie enuma z sumy. Można operatory utożsamiać z + oraz –, jednak na końcu zobaczymy jak to wygląda dokładnie z punktu widzenia algebry Bool’a. Warto również, że dzięki atrybutowi Flags, kompilator rozpoznaje złączenia i wyświetla wszystkie elementy (a nie liczbę utworzoną przez OR).

W kodzie jednak dużo częściej będziemy musieli sprawdzić czy dana suma zawiera konkretny element (rzadko kiedy wyświetlamy enum’y jak pokazano w kodzie wyżej). Niezbędny jest zatem mechanizm umożliwiający sprawdzenie czy dany element znajduje się zbiorze. Służy do tego operator AND:

bool test = (meetingDays & Days.Thursday) == Days.Thursday;
Console.WriteLine("Thursday {0} a meeting day.", test == true ? "is" : "is not");

Przyjrzyjmy się teraz wartościom binarnym enuma:

00000000  0

00000001  1

00000010  2

00000100  4

00001000  16

00010000  32

00100000  64

Złączmy dwie ostatnie wartości tzn:

00010000  OR  00100000  = 00110000

Jak widać żaden bit nie został napisany (dzięki wykorzystanemu kodowaniu). Zobaczmy jeszcze operator AND służący do testowania czy dany element znajduje się w zbiorze:

00110000  AND 00100000  == 00100000

Gdybyśmy źle zakodowali wartości enum’a, niektóre bity mogłyby się napisać. Wykorzystując prostą algebrę można w sprytny sposób łączyć liczby.

11 thoughts on “Flagi w Enum”

  1. gdzieś po drodze zjadło 8 w liście wartości. Powinno być 1000 8, 10000 16, etc

    tak swoją drogą to ładnie też wygląda zapis hex

    1 : 0x1
    2 : 0x2
    4 : 0x4
    8 : 0x8
    16 : 0x10
    36 : 0x20
    64 : 0x40
    128 : 0x80
    256 : 0x100
    512 : 0x200
    etc. wystarczy pamiętać że liczymy do 8. Przydatne przy przerabianiu longów na flagi 🙂

  2. @UP
    A swoją drogą to najładniej wygląda zapis
    None = 0,
    Sunday = 1 << 1,
    Monday = 1 << 2,
    Tuesday = 1 << 3,
    Wednesday = 1 << 4

    Kwestia gustu 😉

  3. Bardzo fajny wpis. Jest też jedna faja rzecz w samej definicji enuma można zrobić tak:

    public enum Flagi {
    None = 0,
    First = 1,
    Second = 2,
    Third = 4,
    All = First | Second | Third
    }

    Czyli można definiować elementy enuma z innych elementów, przy zapisie flagowym bardzo fajnie można zdefiniować, enum :).

  4. nie, nie popelniles bledu. 🙂 Dokladnie tak jest w MSDN. I w komentarzach jest ten sam bug wylapany, ktory rzucil mi sie w oczy u Ciebie w kodzie 😉

  5. fajny art chociaz moglby byc dokladniejsze ale koledzy w komentarzach dodali sporo. Doczytalem jeszcze w MSDN ze do testowania czy cos znajduje sie w zbiorze mozna uzyc .HasFlag:
    “A convenient way to test whether a flag is set in a numeric value is to call the instance HasFlag method, as shown in the following example.”
    Pets familyPets = Pets.Dog | Pets.Cat;
    if (familyPets.HasFlag(Pets.Dog))
    Console.WriteLine(“The family has a dog.”);
    // The example displays the following output:
    // The family has a dog.

  6. @bin

    Przy .HasFlag mamy boxing (dany enum jest valuetype a System.Enum z którego jest ta metoda reference) także nie jest to moim zdaniem dobry pomysł na sprawdzanie flag.

Leave a Reply

Your email address will not be published.