web-dev-qa-db-fra.com

Utiliser un masque de bits en C #

Disons que j'ai le suivant

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

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

Je sais que 10 soit 1010

mais comment puis-je faire une 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

En ce moment, tout ce que je peux penser est de vérifier si le nombre 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 réel, cela semble peu pratique. Quelle meilleure façon d’utiliser un masque pour vérifier si je remplis ou non la condition de karen?

Je peux penser au décalage gauche puis retour puis retour droit puis retour pour effacer des éléments clairs autres que celui qui m'intéresse, mais cela semble aussi excessivement complexe.

87
Matt

La méthode traditionnelle consiste à utiliser l'attribut Flags sur un enum:

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

Ensuite, vous rechercherez 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 au niveau des bits logiques peuvent être difficiles à retenir, je me simplifie donc la vie avec un FlagsHelper classe *:

// 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 en tant que:

Names names = Names.Susan | Names.Bob;

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

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

Notez que je pourrais aussi ajouter Karen à l'ensemble en procédant comme suit:

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

Et je pourrais retirer Susan de la même manière:

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

* Comme l'a souligné Porges, un équivalent de la méthode IsSet ci-dessus existe déjà dans .NET 4.0: Enum.HasFlag . Les méthodes Set et Unset ne semblent toutefois pas avoir d'équivalent; donc je dirais quand même que cette classe a du mérite.


Remarque: Utiliser enums 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 aussi bien.

181
Dan Tao
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Le bitwise 'et' masquera tout sauf le bit qui "représente" Karen. Tant que chaque personne est représentée par une position de bit unique, 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
}
20
eldarerathis

J'ai inclus un exemple ici qui montre comment vous pouvez stocker le masque dans une colonne de la base de données en tant qu'int, et comment vous pourrez le rétablir ultérieurement:

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;
11
Nick Wright

Pour combiner les masques de bits que vous souhaitez utiliser, --o. Dans le cas trivial où chaque valeur que vous combinez a exactement 1 bit (comme dans votre exemple), cela revient à les ajouter. Cependant, si vous avez des bits qui se chevauchent, o 'ing gère le cas avec élégance.

Pour décoder les masques de bits, vous et votre valeur avec un masque, comme suit:

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

Une autre très bonne raison d'utiliser un masque de bits contre des bools individuels est en tant que développeur Web. Lors de l'intégration d'un site Web à un autre, il est souvent nécessaire d'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 en tant que masque de bits que d'envoyer plusieurs valeurs en tant que bools. Je sais qu'il y a d'autres moyens d'envoyer des données (GET, POST, etc.), mais un simple paramètre sur la chaîne de requête suffit le plus souvent pour les éléments non sensibles. Essayez d'envoyer 128 valeurs bool 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 url dans les navigateurs.

0
Greg Osborne

Moyen facile:

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

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

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 */}
0
A-Sharabiani