Używanie maski bitowej w C #

98

Powiedzmy, że mam następujące rzeczy

int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000

i przekazuję 10 (8 + 2) jako parametr metody i chcę to zdekodować, aby oznaczało susan i karen

Wiem, że 10 to 1010

ale jak mogę zastosować logikę, aby sprawdzić, czy określony bit jest zaznaczony jako włączony

if (condition_for_karen) // How to quickly check whether effective karen bit is 1

W tej chwili jedyne, o czym myślę, to sprawdzić, czy liczba, którą zdałem, jest

14 // 1110
12 // 1100
10 // 1010
8 //  1000

Kiedy mam większą liczbę rzeczywistych bitów w moim scenariuszu ze świata rzeczywistego, wydaje się to niepraktyczne, jaki jest lepszy sposób użycia maski, aby sprawdzić, czy spełniam warunek tylko dla Karen?

Mogę pomyśleć o przesunięciu w lewo, a następnie przesunięciu w prawo, a potem z powrotem, aby wyczyścić fragmenty inne niż ten, który mnie interesuje, ale to również wydaje się zbyt skomplikowane.

Matt
źródło
7
Musiałem tylko skomentować użycie. Jeśli wykonujesz operacje bitowe, powinieneś używać tylko operatorów manipulujących bitami. tzn. pomyśl o tym jako (8 | 2), a nie (8 + 2).
Jeff Mercado,
Karen chciałaby również natychmiast porozmawiać z twoim przełożonym.
Krythic

Odpowiedzi:

198

Tradycyjnym sposobem jest użycie Flagsatrybutu w enum:

[Flags]
public enum Names
{
    None = 0,
    Susan = 1,
    Bob = 2,
    Karen = 4
}

Następnie sprawdź konkretną nazwę w następujący sposób:

Names names = Names.Susan | Names.Bob;

// evaluates to true
bool susanIsIncluded = (names & Names.Susan) != Names.None;

// evaluates to false
bool karenIsIncluded = (names & Names.Karen) != Names.None;

Logiczne kombinacje bitowe mogą być trudne do zapamiętania, więc ułatwiam sobie życie dzięki FlagsHelperklasie *:

// The casts to object in the below code are an unfortunate necessity due to
// C#'s restriction against a where T : Enum constraint. (There are ways around
// this, but they're outside the scope of this simple illustration.)
public static class FlagsHelper
{
    public static bool IsSet<T>(T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        return (flagsValue & flagValue) != 0;
    }

    public static void Set<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue | flagValue);
    }

    public static void Unset<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue & (~flagValue));
    }
}

Pozwoliłoby mi to przepisać powyższy kod jako:

Names names = Names.Susan | Names.Bob;

bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);

bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);

Uwaga mogłem również dodać Karendo zestawu robiąc to:

FlagsHelper.Set(ref names, Names.Karen);

I mógłbym usunąć Susanw podobny sposób:

FlagsHelper.Unset(ref names, Names.Susan);

* Jak Porges wskazał, równowartość tej IsSetmetody powyżej istnieje już w .NET 4.0: Enum.HasFlag. SetI Unsetmetody nie wydają się mieć odpowiedniki, choć; więc nadal powiedziałbym, że ta klasa ma pewne zalety.


Uwaga: używanie wyliczeń to tylko konwencjonalny sposób rozwiązania tego problemu. Możesz całkowicie przetłumaczyć cały powyższy kod, aby zamiast tego używał ints i będzie działać równie dobrze.

Dan Tao
źródło
14
+1 za bycie pierwszym kodem, który faktycznie działa. Możesz także to zrobić (names & Names.Susan) == Names.Susan, co nie wymaga rozszerzenia None.
Matthew Flaschen,
1
@Matthew: O tak, słuszna uwaga. Wydaje mi się, że po prostu nabrałem zwyczaju definiowania Nonewartości dla wszystkich moich wyliczeń, ponieważ wydaje mi się, że jest to wygodne w wielu scenariuszach.
Dan Tao,
31
To jest wbudowane, nie potrzebujesz swoich pomocniczych metod ...var susanIsIncluded = names.HasFlag(Names.Susan);
porge
2
@Porges: Wow, nie mam pojęcia, jak to przegapiłem… dzięki za zwrócenie uwagi! (Wygląda na to, że jest dostępna tylko od .NET 4.0, chociaż ... również, nie ma odpowiednika dla tej Setmetody. Więc powiedziałbym, że metody pomocnicze nie są całkowicie bezwartościowe.)
Dan Tao
6
Zauważ, że używanie names.HasFlag(Names.Susan)jest takie, (names & Names.Susan) == Names.Susanktóre nie zawsze jest podobne (names & Names.Susan) != Names.None. Na przykład, jeśli sprawdzisz, names.HasFlag(Names.none)czynames.HasFlag(Names.Susan|Names.Karen)
ABCade
20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Bitowe „i” maskują wszystko z wyjątkiem bitu „reprezentującego” Karen. Jeśli każda osoba jest reprezentowana przez jedną pozycję bitową, możesz sprawdzić wiele osób za pomocą prostego:

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}
eldarerathis
źródło
12

Dołączyłem tutaj przykład, który demonstruje, jak można przechowywać maskę w kolumnie bazy danych jako int i jak można ją później przywrócić:

public enum DaysBitMask { Mon=0, Tues=1, Wed=2, Thu = 4, Fri = 8, Sat = 16, Sun = 32 }


DaysBitMask mask = DaysBitMask.Sat | DaysBitMask.Thu;
bool test;
if ((mask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((mask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((mask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

// Store the value
int storedVal = (int)mask;

// Reinstate the mask and re-test
DaysBitMask reHydratedMask = (DaysBitMask)storedVal;

if ((reHydratedMask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((reHydratedMask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((reHydratedMask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;
Nick Wright
źródło
Zrobiłem coś podobnego, ale definiując maskę zrobiłem Mon = Math.Power (2, 0), Tues = Math.Pow (2, 1), Wed = Math.Pow (2, 2) itd., Więc pozycja bitu jest trochę bardziej oczywiste dla tych, którzy nie są przyzwyczajeni do konwersji binarnej na dziesiętną. Blindy jest również dobry, ponieważ zmienia się w wynik boolowski, przesuwając zamaskowany bit.
Analog Arsonist
7

Aby połączyć maski bitowe, które chcesz użyć bitowe- lub . W trywialnym przypadku, w którym każda łączona wartość ma dokładnie 1 bit (tak jak w przykładzie), jest to równoważne z dodaniem ich. Jeśli jednak masz nakładające się bity, lub je obsługujesz z wdziękiem.

Aby zdekodować maski bitowe ty i twoją wartość za pomocą maski, na przykład:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
Blindy
źródło
1
Nie można użyć liczby całkowitej jako wartości logicznej w języku C #.
Shadow
7

Łatwy sposób:

[Flags]
public enum MyFlags {
    None = 0,
    Susan = 1,
    Alice = 2,
    Bob = 4,
    Eve = 8
}

Aby ustawić flagi, użyj operatora logicznego „lub” |:

MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;

Aby sprawdzić, czy flaga jest uwzględniona, użyj HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
A-Sharabiani
źródło
Wygląda na to, że wszystkie te informacje zostały już podane powyżej. Jeśli podajesz jakieś nowe informacje, powinieneś je wyraźnie zaznaczyć.
sonyisda1
1
prosty przykład użycia HasFlag()i [Flags]nie został podany w innych odpowiedziach.
A-Sharabiani
0

Innym naprawdę dobrym powodem do korzystania z maski bitowej w porównaniu z indywidualnymi wartościami logicznymi jest to, że jako programista internetowy podczas integracji jednej witryny z drugą często musimy wysyłać parametry lub flagi w zapytaniu. Dopóki wszystkie twoje flagi są binarne, znacznie prostsze jest użycie pojedynczej wartości jako maski bitowej niż wysyłanie wielu wartości jako bools. Wiem, że istnieją inne sposoby wysyłania danych (GET, POST itp.), Ale prosty parametr w kwerendzie jest wystarczający przez większość czasu w przypadku elementów niewrażliwych. Spróbuj wysłać 128 wartości bool w zapytaniu, aby komunikować się z witryną zewnętrzną. Daje to również dodatkową możliwość nie przekraczania limitu zapytań dotyczących adresów URL w przeglądarkach

Greg Osborne
źródło
Niezupełnie odpowiedź na pytanie PO - powinien był być komentarzem.
sonyisda1