Pourquoi les autorisations enum ont-elles souvent des valeurs 0, 1, 2, 4?

159

Pourquoi les gens utilisent-ils toujours des valeurs d'énumération comme 0, 1, 2, 4, 8et non 0, 1, 2, 3, 4?

Cela a-t-il quelque chose à voir avec les opérations sur les bits, etc.?

J'apprécierais vraiment un petit extrait de code sur la façon dont cela est utilisé correctement :)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}
Pascal
la source
1
duplication possible de Enum as Flag utilisant, paramétrant et décalant
Henk Holterman
25
Je ne suis pas d'accord sur le vote dupe.
zzzzBov
La manière UNIX de définir l'autorisation est également basée sur la même logique.
Rudy
3
@Pascal: Vous trouverez peut-être utile de lire sur Bitwise OR (et Bitwise AND ), qui est ce que |(et &) représentent. Les différentes réponses supposent que vous la connaissez.
Brian
2
@IAdapter Je peux voir pourquoi vous pensez cela, car la réponse aux deux est la même, mais je pense que les questions sont différentes. L'autre question demande simplement un exemple ou une explication de l'attribut Flags en C #. Cette question semble concerner le concept de bit flags et les principes fondamentaux derrière eux.
Jeremy S

Réponses:

268

Parce qu'ils sont des pouvoirs de deux et que je peux le faire:

var permissions = Permissions.Read | Permissions.Write;

Et peut-être plus tard ...

if( (permissions & Permissions.Write) == Permissions.Write )
{
    // we have write access
}

C'est un champ de bits, où chaque bit défini correspond à une autorisation (ou à toute autre valeur à laquelle correspond logiquement la valeur énumérée). Si ceux-ci étaient définis comme cela, 1, 2, 3, ...vous ne seriez pas en mesure d'utiliser des opérateurs au niveau du bit de cette façon et d'obtenir des résultats significatifs. Pour approfondir ...

Permissions.Read   == 1 == 00000001
Permissions.Write  == 2 == 00000010
Permissions.Delete == 4 == 00000100

Remarquez un modèle ici? Maintenant, si nous prenons mon exemple original, à savoir,

var permissions = Permissions.Read | Permissions.Write;

Ensuite...

permissions == 00000011

Voir? Les bits Readet Writesont tous deux définis, et je peux le vérifier indépendamment (notez également que le Deletebit n'est pas défini et que cette valeur ne transmet donc pas l'autorisation de suppression).

Il permet de stocker plusieurs indicateurs dans un seul champ de bits.

Ed S.
la source
2
@Malcolm: C'est vrai; myEnum.IsSet. Je suis d'avis que c'est une abstraction complètement inutile et ne sert qu'à réduire la frappe, mais meh
Ed S.
1
Bonne réponse, mais vous devriez mentionner pourquoi l'attribut Flags est appliqué, et quand vous ne voudriez pas appliquer également des Flags à certaines énumérations.
Andy
3
@Andy: En fait, l' Flagsattribut ne fait guère plus que vous donner une «jolie impression» iirc. Vous pouvez utiliser une valeur énumérée comme indicateur quelle que soit la présence de l'attribut.
Ed S.
3
@detly: Parce que les instructions if en C # nécessitent une expression booléenne. 0n'est pas false; falseest false. Vous pourriez cependant écrire if((permissions & Permissions.Write) > 0).
Ed S.
2
Au lieu du `` délicat '' (permissions & Permissions.Write) == Permissions.Write, vous pouvez maintenant utiliserenum.HasFlag()
Louis Kottmann
147

Si ce n'est toujours pas clair d'après les autres réponses, pensez-y comme ceci:

[Flags] 
public enum Permissions 
{   
   None = 0,   
   Read = 1,     
   Write = 2,   
   Delete = 4 
} 

est juste une manière plus courte d'écrire:

public enum Permissions 
{   
    DeleteNoWriteNoReadNo = 0,   // None
    DeleteNoWriteNoReadYes = 1,  // Read
    DeleteNoWriteYesReadNo = 2,  // Write
    DeleteNoWriteYesReadYes = 3, // Read + Write
    DeleteYesWriteNoReadNo = 4,   // Delete
    DeleteYesWriteNoReadYes = 5,  // Read + Delete
    DeleteYesWriteYesReadNo = 6,  // Write + Delete
    DeleteYesWriteYesReadYes = 7, // Read + Write + Delete
} 

Il existe huit possibilités, mais vous pouvez les représenter sous forme de combinaisons de quatre membres seulement. S'il y avait seize possibilités, vous pourriez les représenter comme des combinaisons de seulement cinq membres. S'il y avait quatre milliards de possibilités, vous pourriez les représenter comme des combinaisons de seulement 33 membres! Il est évidemment préférable de n'avoir que 33 membres, chacun (sauf zéro) une puissance de deux, que d'essayer de nommer quatre milliards d'éléments dans une énumération.

Eric Lippert
la source
32
+1 pour l'image mentale d'un enumavec quatre milliards de membres. Et le plus triste, c'est que quelqu'un l'a probablement essayé.
Daniel Pryden
23
@DanielPryden En tant que lecteur quotidien du Daily WTF, je le crois.
moelleux
1
2 ^ 33 = ~ 8,6 milliards. Pour 4 milliards de valeurs différentes, vous n'avez besoin que de 32 bits.
un CVn du
5
@ MichaelKjörling l'un des 33 est pour le 0 par défaut
ratchet freak
@ MichaelKjörling: Pour être juste, il n'y a que 32 membres qui sont des puissances de 2, puisque 0 n'est pas une puissance de deux. Donc "33 membres, chacun une puissance de deux" n'est pas exactement correct (sauf si vous comptez 2 ** -infinitycomme une puissance de deux).
Brian
36

Parce que ces valeurs représentent des emplacements de bits uniques en binaire:

1 == binary 00000001
2 == binary 00000010
4 == binary 00000100

etc., donc

1 | 2 == binary 00000011

ÉDITER:

3 == binary 00000011

3 en binaire est représenté par une valeur de 1 à la fois à la place des unités et à la place des deux. C'est en fait la même que la valeur 1 | 2. Ainsi, lorsque vous essayez d'utiliser les emplacements binaires comme indicateurs pour représenter un état, 3 n'est généralement pas significatif (à moins qu'il n'y ait une valeur logique qui est en fait la combinaison des deux)

Pour plus de précisions, vous pouvez étendre votre exemple d'énumération comme suit:

[Flags]
public Enum Permissions
{
  None = 0,   // Binary 0000000
  Read = 1,   // Binary 0000001
  Write = 2,  // Binary 0000010
  Delete = 4, // Binary 0000100
  All = 7,    // Binary 0000111
}

Par conséquent , dans je Permissions.All, j'ai aussi implicitement Permissions.Read, Permissions.WriteetPermissions.Delete

Chris Shain
la source
et quel est le problème avec 2 | 3?
Pascal
1
@Pascal: Parce qu'il 3est 11binaire, c'est-à-dire qu'il ne correspond pas à un seul bit défini, vous perdez donc la possibilité de mapper 1 bit dans une position arbitraire à une valeur significative.
Ed S.
8
@Pascal d' autres termes, 2|3 == 1|3 == 1|2 == 3. Donc , si vous avez une valeur binaire avec 00000011, et vos drapeaux indiquaient des valeurs 1, 2et 3, alors vous ne savez pas si cette valeur représente 1 and 3, 2 and 3, 1 and 2ou only 3. Cela le rend beaucoup moins utile.
yshavit
10
[Flags]
public Enum Permissions
{
    None   =    0; //0000000
    Read   =    1; //0000001
    Write  = 1<<1; //0000010
    Delete = 1<<2; //0000100
    Blah1  = 1<<3; //0001000
    Blah2  = 1<<4; //0010000
}

Je pense qu'écrire comme ça est plus facile à comprendre et à lire, et vous n'avez pas besoin de le calculer.

Bulldozer
la source
5

Ceux-ci sont utilisés pour représenter des indicateurs de bits qui permettent des combinaisons de valeurs d'énumération. Je pense que c'est plus clair si vous écrivez les valeurs en notation hexadécimale

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
JaredPar
la source
4
@Pascal: Peut-être que c'est plus lisible pour vous à ce stade, mais à mesure que vous gagnez de l'expérience, la visualisation des octets en hexadécimal devient une seconde nature. Deux chiffres en hexadécimal correspondent à un octet correspond à 8 bits (enfin ... un octet est généralement de 8 bits de toute façon ... pas toujours vrai, mais pour cet exemple, il est correct de généraliser).
Ed S.
5
@Pascal rapide, qu'obtenez-vous lorsque vous multipliez 4194304par 2? Et pourquoi pas 0x400000? Il est beaucoup plus facile à reconnaître 0x800000comme la bonne réponse que 8388608, et il est également moins sujet aux erreurs de taper la valeur hexadécimale.
phoog
6
Il est beaucoup plus facile de dire, en un coup d'œil, si vos drapeaux sont correctement réglés (c'est-à-dire, sont des puissances de 2), si vous utilisez hex. Est-ce 0x10000qu'une puissance de deux? Oui, il commence par 1, 2, 4 ou 8 et a ensuite tous les 0. Vous n'avez pas besoin de traduire mentalement 0x10 en 16 (bien que cela devienne probablement une seconde nature), pensez simplement à "une puissance de 2".
Brian
1
Je suis absolument d'accord avec Jared pour dire que c'est beaucoup plus facile à noter en hexadécimal. vous utilisez juste 1 2 4 8 et shift
bevacqua
1
Personnellement, je préfère simplement utiliser par exemple P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Je ne sais pas si ce genre de pliage constant fonctionne en C #, mais cela fonctionne très bien en C / C ++ (ainsi qu'en Java, je pense).
moelleux
1

C'est vraiment plus un commentaire, mais comme cela ne prendrait pas en charge le formatage, je voulais simplement inclure une méthode que j'ai utilisée pour configurer les énumérations d'indicateurs:

[Flags]
public enum FlagTest
{
    None = 0,
    Read = 1,
    Write = Read * 2,
    Delete = Write * 2,
    ReadWrite = Read|Write
}

Je trouve cette approche particulièrement utile lors du développement dans le cas où vous souhaitez conserver vos drapeaux par ordre alphabétique. Si vous décidez que vous devez ajouter une nouvelle valeur d'indicateur, vous pouvez simplement l'insérer par ordre alphabétique et la seule valeur que vous devez modifier est celle qu'elle précède maintenant.

Notez, cependant, qu'une fois qu'une solution est publiée sur n'importe quel système de production (en particulier si l'énumération est exposée sans un couplage étroit, comme sur un service Web), il est fortement déconseillé de modifier toute valeur existante dans l'énumération.

Mike Guthrie
la source
1

Il y a beaucoup de bonnes réponses à celle-ci… Je vais juste dire .. si vous n'aimez pas, ou ne pouvez pas facilement saisir ce que la <<syntaxe essaie d'exprimer .. Personnellement, je préfère une alternative (et oserais-je dire, un style de déclaration d'énumération simple ) …

typedef NS_OPTIONS(NSUInteger, Align) {
    AlignLeft         = 00000001,
    AlignRight        = 00000010,
    AlignTop          = 00000100,
    AlignBottom       = 00001000,
    AlignTopLeft      = 00000101,
    AlignTopRight     = 00000110,
    AlignBottomLeft   = 00001001,
    AlignBottomRight  = 00001010
};

NSLog(@"%ld == %ld", AlignLeft | AlignBottom, AlignBottomLeft);

LOG 513 == 513

Tellement plus facile (pour moi du moins) à comprendre. Alignez-les… ​​décrivez le résultat que vous désirez, obtenez le résultat que vous VOULEZ .. Aucun «calcul» nécessaire.

Alex Gray
la source