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