Verwenden einer Bitmaske in C #

97

Angenommen, ich habe Folgendes

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

und ich übergebe 10 (8 + 2) als Parameter an eine Methode und möchte dies dekodieren, um Susan und Karen zu bedeuten

Ich weiß, dass 10 1010 ist

aber wie kann ich eine Logik machen, um zu sehen, ob ein bestimmtes Bit wie in eingecheckt ist

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

Im Moment kann ich nur daran denken, zu überprüfen, ob die Nummer, die ich übergeben habe, ist

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

Wenn ich in meinem realen Szenario eine größere Anzahl von tatsächlichen Bits habe, erscheint dies unpraktisch. Was ist eine bessere Möglichkeit, mit einer Maske zu überprüfen, ob ich die Bedingung für nur Karen erfülle oder nicht?

Ich kann mir vorstellen, nach links und zurück zu wechseln, dann nach rechts und dann zurück, um andere Teile als die zu löschen, an denen ich interessiert bin, aber dies scheint auch zu komplex zu sein.

Matt
quelle
7
Musste nur die Nutzung kommentieren. Wenn Sie Bitoperationen ausführen, sollten Sie nur Bitmanipulationsoperatoren verwenden. Stellen Sie sich das als (8 | 2) vor, nicht als (8 + 2).
Jeff Mercado
Karen möchte auch sofort mit Ihrem Manager sprechen.
Krythic

Antworten:

198

Der traditionelle Weg, dies zu tun, besteht darin, das FlagsAttribut für Folgendes zu verwenden enum:

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

Dann würden Sie wie folgt nach einem bestimmten Namen suchen:

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;

Logische bitweise Kombinationen können schwer zu merken sein, daher mache ich mir das Leben mit einer FlagsHelperKlasse * leichter :

// 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));
    }
}

Dies würde es mir ermöglichen, den obigen Code wie folgt umzuschreiben:

Names names = Names.Susan | Names.Bob;

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

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

Hinweis: Ich könnte Karendem Set auch Folgendes hinzufügen :

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

Und ich könnte Susanauf ähnliche Weise entfernen :

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

* Wie Porges hervorhob, IsSetgibt es in .NET 4.0 bereits ein Äquivalent der oben genannten Methode : Enum.HasFlag. Die Methoden Setund Unsetscheinen jedoch keine Entsprechungen zu haben. Daher würde ich immer noch sagen, dass diese Klasse einen gewissen Wert hat.


Hinweis: Die Verwendung von Aufzählungen ist nur die herkömmliche Methode, um dieses Problem zu lösen. Sie können den gesamten obigen Code vollständig übersetzen, um stattdessen Ints zu verwenden, und es wird genauso gut funktionieren.

Dan Tao
quelle
14
+1 für den ersten Code, der tatsächlich funktioniert. Sie können auch tun (names & Names.Susan) == Names.Susan, was keine erfordert None.
Matthew Flaschen
1
@ Matthew: Oh ja, guter Punkt. Ich glaube, ich habe die Gewohnheit, immer einen NoneWert für alle meine Aufzählungen zu definieren, da ich finde, dass dies in vielen Szenarien praktisch ist.
Dan Tao
30
Dies ist eingebaut, Sie brauchen keine var susanIsIncluded = names.HasFlag(Names.Susan);
Hilfsmethoden
2
@Porges: Wow, keine Ahnung, wie ich das verpasst habe ... danke, dass du darauf hingewiesen hast! (Sieht so aus, als ob es nur ab .NET 4.0 verfügbar ist ... außerdem gibt es kein Äquivalent für die SetMethode. Daher würde ich sagen, dass die Hilfsmethoden zumindest nicht völlig wertlos sind.)
Dan Tao
6
Beachten Sie, dass die Verwendung names.HasFlag(Names.Susan)wie ist, (names & Names.Susan) == Names.Susanwas nicht immer wie ist (names & Names.Susan) != Names.None. Zum Beispiel, wenn Sie überprüfen, ob names.HasFlag(Names.none)odernames.HasFlag(Names.Susan|Names.Karen)
ABCade
20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Das bitweise 'und' maskiert alles außer dem Bit, das Karen "darstellt". Solange jede Person durch eine einzelne Bitposition dargestellt wird, können Sie mehrere Personen mit einem einfachen:

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}
Eldarerathis
quelle
12

Ich habe hier ein Beispiel beigefügt, das zeigt, wie Sie die Maske in einer Datenbankspalte als int speichern und wie Sie die Maske später wieder herstellen können:

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
quelle
Ich habe etwas Ähnliches gemacht, aber beim Definieren der Maske habe ich Mon = Math.Power (2, 0), Di = Math.Pow (2, 1), Mi = Math.Pow (2, 2) usw. gemacht, also die Bitposition ist etwas offensichtlicher für diejenigen, die nicht an die Umwandlung von Binär in Dezimal gewöhnt sind. Blindy's ist auch gut, da es durch Verschieben des maskierten Bits zu einem booleschen Ergebnis wird.
Analoger Brandstifter
7

Um Bitmasken zu kombinieren, möchten Sie bitweise oder verwenden . In dem trivialen Fall, in dem jeder Wert, den Sie kombinieren, genau 1 Bit enthält (wie in Ihrem Beispiel), entspricht dies dem Hinzufügen. Wenn Sie jedoch überlappende Bits haben oder diese bearbeiten, wird der Fall ordnungsgemäß behandelt.

So entschlüsseln Sie die Bitmasken Sie und Ihren Wert mit einer Maske wie folgt :

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
Blind
quelle
1
Sie können in C # keine Ganzzahl als Booleschen Wert verwenden.
Schatten
6

Einfacher Weg:

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

Verwenden Sie zum Setzen der Flags den logischen Operator "oder" |:

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

Und um zu überprüfen, ob eine Flagge enthalten ist, verwenden Sie HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
A-Sharabiani
quelle
Scheint, als ob all diese Informationen bereits oben bereitgestellt wurden. Wenn Sie neue Informationen angeben, sollten Sie diese deutlich markieren.
Sonyisda1
1
Ein einfaches Beispiel für die Verwendung von HasFlag()und [Flags]wurde in anderen Antworten nicht angegeben.
A-Sharabiani
0

Ein weiterer wirklich guter Grund, eine Bitmaske gegen einzelne Bools zu verwenden, ist, dass wir als Webentwickler bei der Integration einer Website in eine andere häufig Parameter oder Flags im Querystring senden müssen. Solange alle Ihre Flags binär sind, ist es viel einfacher, einen einzelnen Wert als Bitmaske zu verwenden, als mehrere Werte als Bools zu senden. Ich weiß, dass es andere Möglichkeiten gibt, Daten zu senden (GET, POST usw.), aber ein einfacher Parameter auf dem Querystring reicht meistens für nicht sensible Elemente aus. Versuchen Sie, 128 Bool-Werte auf einem Querystring zu senden, um mit einer externen Site zu kommunizieren. Dies bietet auch die zusätzliche Möglichkeit, das Limit für URL-Abfrageringe in Browsern nicht zu überschreiten

Greg Osborne
quelle
Keine wirkliche Antwort auf die Frage des OP - hätte ein Kommentar sein sollen.
Sonyisda1