Accueil Nos publications Blog C# et les Enums Binaires

C# et les Enums Binaires

Pour faire de beaux reves

Les énumérations binaires permettent à une variable de contenir plusieurs valeurs d’un type enum. Accusés d’être difficiles à maintenir, elles sont peu utilisées et très souvent méconnues. Cependant, elles peuvent s’avérer très utiles, notamment lors du stockage des informations en base.

Scénario Type

Chez un grand site éditorialiste, appelons-le mondiosport, les articles possèdent historiquement un HighlightType, une sorte de catégorie. Les valeurs possibles des Highlights sont bien définies {Scoop, Live, Video, Audio, None}. En base, la table ‘article’ contient un champ entier ‘Highlight’ dans lequel la valeur de l’highlight est stockée.

Seulement aujourd’hui, c’est le branle-bas de combat : les éditorialistes veulent écrire un article qui est à la fois un Scoop et une Vidéo ! Ils réfléchissent aussi à plein d’autres combinaisons tordues qui pourraient leurs être utiles.

La question qui se pose alors est :
Comment faire pour répercuter ce changement métier dans le code, et dans la base, avec le moins d’impact possible?

1ère idée

Faire une table de référence contenant les valeurs possibles des highlights et une table de relation. Seul hic : la table ‘article’ contient plusieurs millions d’entrées, faire ainsi rajouterait une jointure à chaque instruction SELECT ce qui serait coûteux.

2ème idée

Faire une colonne booléene par valeur possible dans la table article. Effectivement ça n’alourdirait pas le SELECT, ça ne rendrait pas la base plus volumineuse (un champ int prend autant de place que 32 champs booléens non nuls). Seulement, ça changerait la structure de la table centrale de la base de données, la maintenance risquerait de prendre du temps. Pire, le jour où l’on voudra rajouter une valeur à l’énumération, il faudra rajouter un champ dans la table, c’est pas top finalement.

3ème idée

C’est là que les Enums Binaires vont rentrer en jeu…

Principe

Derrière chaque valeur d’une énumération, se cache un entier. Par défaut, les clés d’énumération sont associées à des valeurs allant un en un en partant de 0. Mais il est tout à fait possible de changer cette “numérotation par défaut”. Avec les enums binaires, on va profiter de l’unicité de la décomposition d’un nombre en puissances de deux :

[Flags]
enum HighlightType : byte
{
None = 0, // 0b0000
Scoop = 1, // 0b0001
Live = 2, // 0b0010
Video = 4, // 0b0100
Audio = 8    // 0b1000
}

En faisant ainsi, chaque valeur de l’enum possible correspond à un bit de l’entier.
Le valeur entière de l’highlight, prise comme une suite de 0 et de 1, peut alors être interprétée comme une suite de booléens. Le nombre 0b0101 voudra par exemple dire Video et Scoop (les deux bits “flaggés” à 1).

L’annotation [Flags] spécifie que le comportement “multi-valeurs” est normal. Cela a pour conséquence de changer le résultat de la méthode ToString() :
((HighlightType)5).ToString() affichera '5' sans et “Scoop, Video” avec.

Ce système permet de stocker une liste de valeurs de l’enum dans un entier sans aucun impact en base (on y stocke toujours un entier dans la base ‘article’).

Nous allons maintenant voir comment manipuler de telles énumérations.

Opérations Binaires (Bitwise operations)

Note : Toutes les opérations reposent sur l’absence de valeur “métier” de l’enum pour l’entier 0 (chaque valeur correspond à un bit à 1). Cependant, je recommande de mettre quand même une valeur pour 0 : HighlightType.None. C’est le strict équivalent de 0 chez les entiers (valeurs par défaut et élément neutre de l’addition).

Pour manipuler des enums binaires, le plus simple est de faire des opérations bits à bits. Les opérateurs bits à bits sont les suivants :

    | : OR bit à bit, voici sa table de vérité :

    a b a|b
    0 0 0
    0 1 1
    1 0 1
    1 1 1

    Exemple : 0b0101 | 0b1000 = 0b1101

  • & : AND bit à bit, voici sa table de vérité :
    a b a&b
    0 0 0
    0 1 0
    1 0 0
    1 1 1

    Exemple : 0b0111 & 0b0010 = 0b0010

  • ^ : XOR bit à bit, voici sa table de vérité
    a b a^b
    0 0 0
    0 1 1
    1 0 1
    1 1 0

    Exemple : 0b0101 ^ 0b1110 = 0b1011

  • ~ (opérateur unaire) : NOT bit à bit qui inverse les bits.
    Exemple : ~0b0101=0b1010

Nous allons maintenant voir comment les utiliser dans des cas concrets.

Manipulations usuelles d’enums binaires

Ajout d’une valeur

L’ajout d’une valeur se fait par l’opérateur |.

HighlightType hl = HighlightType.Scoop | HighlightType.Video; //0b0101

Déterminer la présence d’une valeur

Cela se fait à l’aide d’un “masque binaire” : (a & mask == mask)

HighlightType hl = HighlightType.Scoop | HighlightType.Video; //0b0101
bool isAudio = ( (hl & HighlightType.Audio) == HighlightType.Audio) // false : 0b0101 & 0b1000 = 0b0000

Supprimer une valeur si elle existe

Il suffit d’appliquer un “masque inversé” : (a & ~mask)

HighlightType hl = HighlightType.Scoop | HighlightType.Video; //0b0101
HighlightType hlWithoutVideo = hl & ~HighlightType.Video // 0b0001

Conclusion

Et voilà, ces enums m’ont bien rendu service et je pense qu’on les oublie trop souvent. On peut par exemple s’en servir pour des problématiques de droits associés à des profils :

var userPermissions = Permissions.Select | Permissions.Update;
bool canInsert = ((Permissions.Insert & userPermissions) == Permissions.Insert); // false
bool canUpdate = ((Permissions.Update & userPermissions) == Permissions.Update); // true

Sources/Pour aller plus loin

https://weblogs.asp.net/alessandro/archive/2007/10/02/bitwise-operators-in-c-or-xor-and-amp-amp-not.aspx
https://www.blackwasp.co.uk/CSharpLogicalBitwiseOps.aspx
https://www.codeproject.com/KB/tips/Binary_Guide.aspx
https://www.codeproject.com/KB/cs/CustomFlags.aspx