Utilizzo di una maschera di bit in C #


97

Diciamo che ho quanto segue

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

e passo 10 (8 + 2) come parametro a un metodo e voglio decodificarlo per indicare susan e karen

So che 10 fa 1010

ma come posso fare un po 'di logica per vedere se un bit specifico è controllato come in

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

In questo momento tutto ciò a cui riesco a pensare è controllare se il numero che ho passato è

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

Quando ho un numero maggiore di bit effettivi nel mio scenario del mondo reale, questo sembra poco pratico, qual è un modo migliore per usare una maschera per verificare se soddisfi o meno la condizione per solo karen?

Posso pensare di spostarmi a sinistra, poi di nuovo, quindi a destra e poi di nuovo, per eliminare parti diverse da quella che mi interessa, ma anche questo sembra eccessivamente complesso.


7
Dovevo solo commentare l'utilizzo. Se stai eseguendo operazioni sui bit, dovresti usare solo operatori di manipolazione dei bit. vale a dire, pensalo come (8 | 2), non (8 + 2).
Jeff Mercado

Karen vorrebbe anche parlare con il tuo manager, immediatamente.
Krythic

Risposte:


198

Il modo tradizionale per farlo è usare l' Flagsattributo su un enum:

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

Quindi dovresti controllare un nome particolare come segue:

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;

Le combinazioni logiche bit per bit possono essere difficili da ricordare, quindi mi rendo la vita più facile con una FlagsHelperclasse *:

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

Questo mi consentirebbe di riscrivere il codice sopra come:

Names names = Names.Susan | Names.Bob;

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

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

Nota che potrei anche aggiungere Karenal set in questo modo:

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

E potrei rimuovere Susanin modo simile:

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

* Come Porges sottolineato, l'equivalente del IsSetmetodo sopra già esiste in NET 4.0: Enum.HasFlag. Tuttavia, i metodi Sete Unsetnon sembrano avere equivalenti; quindi direi ancora che questa classe ha dei meriti.


Nota: l'uso delle enumerazioni è solo il modo convenzionale di affrontare questo problema. Puoi tradurre totalmente tutto il codice sopra per usare invece gli int e funzionerà altrettanto bene.


14
+1 per essere il primo codice a funzionare effettivamente. Puoi anche farlo (names & Names.Susan) == Names.Susan, il che non richiede un file None.
Matthew Flaschen

1
@ Matteo: Oh sì, buon punto. Immagino di prendere l'abitudine di definire sempre un Nonevalore per tutte le mie enumerazioni, poiché trovo che sia conveniente in molti scenari.
Dan Tao

30
Questo è integrato, non hai bisogno dei tuoi metodi di supporto ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@Porges: Wow, non ho idea di come mi sia perso ... grazie per averlo fatto notare! (Sembra che sia disponibile solo a partire da .NET 4.0, però ... inoltre, non esiste un equivalente per il Setmetodo. Quindi, direi che i metodi di supporto non sono almeno del tutto privi di valore.)
Dan Tao

6
Nota che usare names.HasFlag(Names.Susan)è come (names & Names.Susan) == Names.Susanche non è sempre come (names & Names.Susan) != Names.None. Ad esempio, se controlli se names.HasFlag(Names.none)onames.HasFlag(Names.Susan|Names.Karen)
ABCade il

20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Il bit per bit 'e' maschererà tutto tranne il bit che "rappresenta" Karen. Finché ogni persona è rappresentata da una singola posizione di bit, puoi controllare più persone con un semplice:

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

12

Ho incluso un esempio qui che dimostra come si potrebbe memorizzare la maschera in una colonna del database come int e come si ripristinerà la maschera in seguito:

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;

Ho fatto qualcosa di simile ma quando ho definito la maschera ho fatto Mon = Math.Power (2, 0), Tues = Math.Pow (2, 1), Wed = Math.Pow (2, 2), ecc. Quindi la posizione del bit è un po 'più ovvio per coloro che non sono abituati alla conversione da binario a decimale. Anche Blindy's è buono poiché si trasforma in un risultato booleano spostando il bit mascherato.
Incendiario analogico

7

Per combinare le maschere di bit si desidera utilizzare bit a bit o . Nel caso banale in cui ogni valore che combini ha esattamente 1 bit (come il tuo esempio), equivale ad aggiungerli. Tuttavia, se si hanno bit sovrapposti, o se li si utilizza, il caso viene gestito con grazia.

Per decodificare le maschere di bit tu e il tuo valore con una maschera, in questo modo:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();

1
Non è possibile utilizzare un numero intero come booleano in C #.
Shadow

6

Modo semplice:

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

Per impostare i flag utilizzare l'operatore logico "or" |:

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

E per verificare se è inclusa una bandiera, utilizzare HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}

Sembra che tutte queste informazioni siano già state fornite sopra. Se stai fornendo nuove informazioni, dovresti contrassegnarle chiaramente.
sonyisda1

1
semplice esempio di utilizzo di HasFlag()e [Flags]non è stato fornito in altre risposte.
A-Sharabiani,

0

Un altro ottimo motivo per utilizzare una maschera di bit rispetto a bool individuali è come sviluppatore web, quando si integra un sito Web con un altro, spesso abbiamo bisogno di inviare parametri o flag nella stringa di query. Finché tutti i tuoi flag sono binari, è molto più semplice usare un singolo valore come maschera di bit piuttosto che inviare più valori come bool. So che ci sono altri modi per inviare dati (GET, POST, ecc.), Ma un semplice parametro sulla stringa di query è il più delle volte sufficiente per gli elementi non sensibili. Prova a inviare 128 valori bool su una stringa di query per comunicare con un sito esterno. Ciò offre anche la possibilità aggiuntiva di non spingere il limite sulle stringhe di query dell'URL nei browser


Non proprio una risposta alla domanda dell'OP, avrebbe dovuto essere un commento.
sonyisda1
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.