Pourquoi utiliser des drapeaux + bitmasks plutôt qu’une série de booléens?

Dans un cas où j’ai un object qui peut être dans un ou plusieurs états true / false, j’ai toujours été un peu flou sur la raison pour laquelle les programmeurs utilisent fréquemment les options flags + bitmasks au lieu d’utiliser simplement plusieurs valeurs booléennes.

C’est partout dans le framework .NET. Vous ne savez pas si c’est le meilleur exemple, mais le framework .NET présente les caractéristiques suivantes:

public enum AnchorStyles { None = 0, Top = 1, Bottom = 2, Left = 4, Right = 8 } 

Donc, étant donné un style d’ancrage, nous pouvons utiliser des masques de bits pour déterminer lequel des états est sélectionné. Cependant, il semble que vous puissiez accomplir la même chose avec une classe / structure AnchorStyle avec des propriétés bool définies pour chaque valeur possible, ou un tableau de valeurs enum individuelles.

Bien sûr, la raison principale de ma question est que je me demande si je devrais suivre une pratique similaire avec mon propre code.

Alors, pourquoi utiliser cette approche?

  • Moins de consommation de mémoire? (il ne semble pas que cela consum moins qu’un tableau / structure de bools)
  • Meilleure performance stack / tas qu’un struct ou un tableau?
  • Des opérations de comparaison plus rapides? Ajout / suppression de valeur plus rapide?
  • Plus pratique pour le développeur qui l’a écrit?

C’était traditionnellement un moyen de réduire l’utilisation de la mémoire. Donc, oui, c’est assez obsolète en C # 🙂

En tant que technique de programmation, il est peut-être obsolète dans les systèmes actuels, et vous seriez bien d’accord pour utiliser un tableau de bool, mais …

Il est rapide de comparer les valeurs stockées sous forme de masque de bits. Utilisez les opérateurs logiques AND et OR et comparez les 2 ints résultants.

Il utilise beaucoup moins de mémoire. Mettre les 4 valeurs de votre exemple dans un masque de bits utiliserait un demi-octet. En utilisant un tableau de bools, le plus susceptible d’utiliser quelques octets pour l’object tableau plus un long mot pour chaque bool. Si vous devez stocker un million de valeurs, vous verrez exactement pourquoi une version à masque de bits est supérieure.

C’est plus facile à gérer, il suffit de traiter avec une seule valeur entière, alors qu’un tableau de bools stockerait très différemment dans, par exemple, une firebase database.

Et, en raison de la disposition de la mémoire, beaucoup plus rapide dans tous les aspects qu’un tableau. C’est presque aussi rapide que d’utiliser un seul entier 32 bits. Nous soaps tous que cela est aussi rapide que vous pouvez obtenir pour les opérations sur les données.

  • Réglage facile de plusieurs drapeaux dans n’importe quel ordre.

  • Facile à enregistrer et obtenir une série de 0101011 à la firebase database.

Entre autres choses, il est plus facile d’append de nouvelles significations de bits à un champ de bits que d’append de nouvelles valeurs booléennes à une classe. Il est également plus facile de copier un champ de bits d’une instance à une autre qu’une série de booléens.

Cela peut également rendre les méthodes plus claires. Imaginez une méthode avec 10 bools vs 1 Bitmask.

En fait, les performances peuvent être meilleures, principalement si votre enum provient d’un octet. Dans ce cas extrême, chaque valeur enum serait représentée par un octet, contenant toutes les combinaisons, jusqu’à 256. Avoir autant de combinaisons possibles avec des booléens conduirait à 256 octets.

Mais même dans ce cas, je ne pense pas que ce soit la vraie raison. La raison pour laquelle je préfère ceux-ci est le pouvoir que C # me donne pour gérer ces enums. Je peux append plusieurs valeurs avec une seule expression. Je peux les enlever aussi. Je peux même comparer plusieurs valeurs à la fois avec une seule expression en utilisant l’énum. Avec des booléens, le code peut devenir, disons, plus verbeux.

Raymond Chen a publié un article sur ce sujet .

Bien sûr, les champs de bits permettent d’économiser la mémoire de données, mais vous devez équilibrer le coût en taille de code, en débogage et en multithreading réduit.

Comme d’autres l’ont dit, son temps est largement passé. Il est tentant de continuer à le faire, car il est amusant de sembler amusant, mais il n’est plus plus efficace, il présente de sérieux inconvénients en termes de maintenance, il ne fonctionne pas bien avec les bases de données, et à moins que vous ne travailliez dans une monde intégré, vous avez assez de mémoire.

Je suggérerais de ne jamais utiliser de drapeaux enum, sauf si vous avez affaire à des limitations de mémoire assez sérieuses (peu probable). Vous devez toujours écrire du code optimisé pour la maintenance.

Avoir plusieurs propriétés booléennes facilite la lecture et la compréhension du code, la modification des valeurs et la fourniture de commentaires Intellisense, sans parler de la réduction du risque de bogues. Si nécessaire, vous pouvez toujours utiliser un champ d’indicateur enum en interne, assurez-vous simplement d’exposer le paramétrage / l’obtention des valeurs avec des propriétés booléennes.

Dans une perspective de modèle de domaine, il modélise simplement la réalité mieux dans certaines situations. Si vous avez trois booléens tels que AccountIsInDefault et IsPreferredCustomer et REQUSalesTaxState, il n’a aucun sens de les append à une seule énumération Drapés et décorés, car ils ne constituent pas trois valeurs distinctes pour le même élément de modèle de domaine.

Mais si vous avez un ensemble de booléens comme:

  [Flags] enum AccountStatus {AccountIsInDefault=1, AccountOverdue=2 and AccountFrozen=4} 

ou

  [Flags] enum CargoState {ExceedsWeightLimit=1, ContainsDangerousCargo=2, IsFlammableCargo=4, ContainsRadioactive=8} 

Ensuite, il est utile de pouvoir stocker l’état total du compte (ou de la cargaison) dans UNE variable … qui représente UN élément de domaine dont la valeur peut représenter toute combinaison d’états possible.

  1. Efficacité spatiale – 1 bit
  2. Gain de temps – les comparaisons de bits sont gérées rapidement par le matériel.
  3. Indépendance linguistique – lorsque les données peuvent être traitées par un certain nombre de programmes différents, vous n’avez pas à vous soucier de la mise en œuvre de booléens dans différentes langues / plateformes.

La plupart du temps, cela ne vaut pas le compromis en termes de maintenance. Cependant, il y a des moments où cela est utile:

  1. Protocoles réseau – vous réduirez considérablement la taille des messages
  2. Logiciel hérité – une fois, j’ai dû append des informations pour pouvoir retrouver un logiciel hérité.

Coût pour modifier l’en-tête: millions de dollars et années d’effort. Coût pour mettre les informations sur 2 octets dans l’entête non utilisés: 0.

Bien sûr, il y avait des coûts supplémentaires dans le code qui accédait à ces informations et les manipulait, mais celles-ci étaient effectuées par des fonctions de sorte qu’une fois que les accesseurs avaient été définis, elles n’étaient pas moins faciles à gérer que l’utilisation de Booleans.

C’est pour la rapidité et l’efficacité. Essentiellement, tout ce que vous travaillez est un seul int.

 if ((flags & AnchorStyles.Top) == AnchorStyles.Top) { //Do stuff }