Utilisation de flags binaires [Flags]

Ce post décrit comment utiliser et définir des flags binaires. Ceux-ci permettent de combiner facilement et lisiblement des propriétés. Un des exemples d’utilisation les plus populaires étant avec les attributs de fichiers :

File.SetAttributes(@"C:\Temp\MonFichier",

FileAttributes.ReadOnly | FileAttributes.Archive | FileAttributes.Temporary);

L'utilisation de flags binaires est recommandée lorsque les propriétés représentées sont liées, non mutuellement exclusives et combinables. Les flags permettent ainsi :

- De rendre le code plus lisible

- De laisser la porte ouverte à de nouvelles propriétés

- D’optimiser certains scénarios de sérialisation

Le principe des flags est le suivant : chaque valeur booléenne est représentée par un seul et unique bit d’un scalaire numérique (byte, short, int, int64…). On considère que la valeur est vraie lorsque le bit correspondant vaut 1, et qu’elle est fausse sinon. Cela permet dans le cas d’un byte (8 bits), de représenter jusqu’à 8 valeurs combinables.

Le programme exemple montre comment utiliser les flags par le biais d’une classe Voiture et d’options qui lui sont applicables.

On déclare tout d’abord une énumération OptionsVoiture, qui définit les valeurs possibles des options, chacune étant associée à une puissance de 2. L’utilisation de MaxValue est une facilité qui permettra de déterminer rapidement si toutes les valeurs ont été cochées. Il est aussi d’usage d’utiliser un type plus grand que nécessaire afin de réserver des bits pour de futures évolutions.

[Flags]

public enum OptionsVoiture: byte

{

    Aucune = 0,

    Aileron = 1,

    ToitOuvrant = 2,

    RetroviseursElectriques = 4,

    VitresTeintees = 8,

    Neons = 16,

    PriseAir = 32,

    Halogenes = 64,

    Rabaissement = 128,

    Toutes = Byte.MaxValue

}

Note : la décoration avec l’attribut optionnel Flags permet simplement aux IDE de proposer une interface conviviale lors de la définition de ce type de valeur (par exemple avec une liste à cocher)

La propriété de Voiture permettant de représenter toutes ces options est de type OptionsVoiture

public OptionsVoiture Options { get; set; }

Et ses valeurs peuvent être définies en combinant les valeurs de l’énumération avec un OU logique :

var vt = new Voiture();

vt.Options = OptionsVoiture.Aileron | OptionsVoiture.ToitOuvrant | OptionsVoiture.Neons;

 

 

La vérification de la valeur d’une Options est faite à l’aide d’un ET logique, ou dans le cas de OptionsVoiture.Toutes d’une simple vérification d’égalité.

if ((this.Options & OptionsVoiture.PriseAir) == OptionsVoiture.PriseAir)
sb.Append("Prise d’air !");

if (this.Options == OptionsVoiture.Toutes) sb.Append("Toutes options ! ");

Il est également très pratique de pouvoir vérifier plusieurs valeurs d’un seul coup. L’exemple qui suit détermine si les options Aileron et Neons sont activées.

var aileronEtNeons = (OptionsVoiture.Aileron | OptionsVoiture.Neons);
bool isAileronEtNeons = (maVoiture.Options & aileronEtNeons) == aileronEtNeons;

Et pour finir, une petite astuce permettant de choisir toutes les options sauf certaines :

var maVoiture = new Voiture();

maVoiture.Options = OptionsVoiture.Toutes;

maVoiture.Options &= ~(OptionsVoiture.Halogenes | OptionsVoiture.Rabaissement);

En ce qui concerne la sérialisation, bien que l’utilisation de flags permette une économie substantielle de mémoire, elle n’est pas la solution parfaite à tous les scénarios. Par exemple, le gain de place peut vite se voir contrecarré par des requêtes SQL plus coûteuses, ainsi qu’une perte au niveau des possibilités d’indexation.

Un grand merci à Olivier Sallaberry, guru des Windows Live Services, pour ses conseils et la relecture de cet article J Et en espérant comme à l’habitude, que cela facilitera vos développements !

 

FlagsExample.cs