Utilisation d'un masque de bits en C #

97

Disons que j'ai ce qui suit

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

et je passe 10 (8 + 2) comme paramètre à une méthode et je veux décoder cela pour signifier susan et karen

Je sais que 10 est 1010

mais comment puis-je faire de la logique pour voir si un bit spécifique est vérifié comme dans

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

Pour le moment, tout ce que je peux penser est de vérifier si le numéro que j'ai passé est

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

Lorsque j'ai un plus grand nombre de bits réels dans mon scénario du monde réel, cela semble peu pratique, quelle est la meilleure façon d'utiliser un masque pour simplement vérifier si je remplis ou non la condition pour juste karen?

Je peux penser à passer à gauche, puis à revenir à droite, puis à revenir à des bits clairs autres que celui qui m'intéresse, mais cela semble aussi trop complexe.

Mat
la source
7
Juste eu à commenter l'utilisation. Si vous effectuez des opérations sur les bits, vous ne devez utiliser que des opérateurs de manipulation de bits. c'est-à-dire, pensez-y comme (8 | 2), pas (8 + 2).
Jeff Mercado
Karen aimerait également parler immédiatement à votre responsable.
Krythic

Réponses:

198

La manière traditionnelle de procéder consiste à utiliser l' Flagsattribut sur un enum:

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

Ensuite, vous vérifieriez un nom particulier comme suit:

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;

Les combinaisons logiques au niveau du bit peuvent être difficiles à retenir, donc je me simplifie la vie avec une 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));
    }
}

Cela me permettrait de réécrire le code ci-dessus comme suit:

Names names = Names.Susan | Names.Bob;

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

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

Notez que je pourrais également ajouter Karenà l'ensemble en faisant ceci:

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

Et je pourrais supprimer Susande la même manière:

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

* Comme Porges a souligné, un équivalent de la IsSetméthode ci - dessus existe déjà dans .NET 4.0: Enum.HasFlag. Les Setet Unsetméthodes ne semblent pas avoir équivalents, cependant; alors je dirais toujours que cette classe a du mérite.


Remarque: l'utilisation d'énumérations n'est que la manière conventionnelle de résoudre ce problème. Vous pouvez totalement traduire tout le code ci-dessus pour utiliser ints à la place et cela fonctionnera tout aussi bien.

Dan Tao
la source
14
+1 pour être le premier code à fonctionner réellement. Vous pouvez également le faire (names & Names.Susan) == Names.Susan, ce qui ne nécessite pas de fichier None.
Matthew Flaschen
1
@Matthew: Oh ouais, bon point. Je suppose que je prends juste l'habitude de toujours définir une Nonevaleur pour toutes mes énumérations, car je trouve que cela finit par être pratique dans de nombreux scénarios.
Dan Tao
30
Ceci est intégré, vous n'avez pas besoin de vos méthodes d'aide ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges
2
@Porges: Wow, je ne sais pas comment j'ai raté ça ... merci de l'avoir signalé! (On dirait qu'il n'est disponible qu'à partir de .NET 4.0, cependant ... aussi, il n'y a pas d'équivalent pour la Setméthode. Donc, je dirais que les méthodes d'assistance ne sont au moins pas totalement sans valeur.)
Dan Tao
6
Notez que l'utilisation names.HasFlag(Names.Susan)est comme (names & Names.Susan) == Names.Susance qui n'est pas toujours comme (names & Names.Susan) != Names.None. Par exemple, si vous vérifiez si names.HasFlag(Names.none)ounames.HasFlag(Names.Susan|Names.Karen)
ABCade
20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Le bit "et" masquera tout sauf le bit qui "représente" Karen. Tant que chaque personne est représentée par une seule position de bit, vous pouvez vérifier plusieurs personnes avec un simple:

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

J'ai inclus un exemple ici qui montre comment vous pourriez stocker le masque dans une colonne de base de données en tant qu'int, et comment vous rétabliriez le masque plus tard:

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
la source
J'ai fait quelque chose de similaire mais lors de la définition du masque, j'ai fait Mon = Math.Power (2, 0), Tuesday = Math.Pow (2, 1), Wed = Math.Pow (2, 2), etc. donc la position du bit est un peu plus évident pour ceux qui ne sont pas habitués à la conversion binaire en décimale. Blindy's est bon aussi car il se transforme en résultat booléen en décalant le bit masqué.
Analog Arsonist
7

Pour combiner des masques de bits, vous souhaitez utiliser au niveau du bit ou . Dans le cas trivial où chaque valeur que vous combinez a exactement 1 bit (comme votre exemple), cela équivaut à les ajouter. Si vous avez des bits qui se chevauchent cependant, ou 'ing les gère le cas avec élégance.

Pour décoder les bitmasks vous et votre valeur avec un masque, comme ceci:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
Aveugle
la source
1
Vous ne pouvez pas utiliser un entier comme booléen en C #.
Shadow
6

Moyen facile:

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

Pour définir les indicateurs, utilisez l'opérateur logique "ou" |:

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

Et pour vérifier si un drapeau est inclus, utilisez HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
A-Sharabiani
la source
On dirait que toutes ces informations ont déjà été fournies ci-dessus. Si vous donnez de nouvelles informations, vous devez les marquer clairement.
sonyisda1
1
un exemple simple d'utilisation des HasFlag()et des [Flags]n'a pas été fourni dans les autres réponses.
A-Sharabiani le
0

Une autre très bonne raison d'utiliser un masque de bits par rapport à des bools individuels est en tant que développeur Web, lors de l'intégration d'un site Web à un autre, nous devons fréquemment envoyer des paramètres ou des indicateurs dans la chaîne de requête. Tant que tous vos indicateurs sont binaires, il est beaucoup plus simple d'utiliser une seule valeur comme masque de bits que d'envoyer plusieurs valeurs sous forme de booléens. Je sais qu'il existe d'autres moyens d'envoyer des données (GET, POST, etc.), mais un simple paramètre sur la chaîne de requête est la plupart du temps suffisant pour les éléments non sensibles. Essayez d'envoyer 128 valeurs booléennes sur une chaîne de requête pour communiquer avec un site externe. Cela donne également la possibilité supplémentaire de ne pas repousser la limite des chaînes de requête d'URL dans les navigateurs

Greg Osborne
la source
Pas vraiment une réponse à la question du PO - aurait dû être un commentaire.
sonyisda1