Un Enum doit-il commencer par un 0 ou un 1?

136

Imaginez que j'ai défini l'énumération suivante:

public enum Status : byte
{
    Inactive = 1,
    Active = 2,
}

Quelle est la meilleure pratique pour utiliser enum? Devrait-il commencer par 1comme l'exemple ci-dessus ou commencer par 0(sans les valeurs explicites) comme ceci:

public enum Status : byte
{
    Inactive,
    Active
}
Acaz Souza
la source
13
Avez-vous vraiment besoin de les numéroter explicitement?
Beurk du
164
Les énumérations ont été créées juste pour que de telles choses ne soient pas importantes.
BoltClock
9
@Daniel - arrgh non! Mieux vaut utiliser une énumération lorsque vous pensez qu'un booléen fera l'affaire que d'utiliser un booléen lorsque vous pensez à une énumération.
AAT
22
@Daniel à cause de la valeur FileNotFound, bien sûr
Joubarc
5
xkcd.com/163 s'applique encore mieux à enum qu'aux index de tableau.
leftaroundabout

Réponses:

161

Directives de conception du cadre :

✔️ Fournissez une valeur de zéro sur les énumérations simples.

Pensez à appeler la valeur quelque chose comme "Aucun". Si une telle valeur n'est pas appropriée pour cette énumération particulière, la valeur par défaut la plus courante pour l'énumération doit être affectée à la valeur sous-jacente de zéro.

Directives de conception de cadre / conception d'énumérations d'indicateur :

❌ ÉVITEZ d'utiliser des valeurs d'énumération d'indicateur de zéro à moins que la valeur ne représente «tous les indicateurs sont effacés» et qu'elle soit nommée de manière appropriée, comme prescrit par la ligne directrice suivante.

✔️ Nommez la valeur zéro des énumérations d'indicateur None. Pour une énumération d'indicateur, la valeur doit toujours signifier «tous les indicateurs sont effacés».

Andrey Taptunov
la source
28
Échouer tôt: si `` Aucun '' n'est pas approprié mais qu'il n'y a pas de valeur logique par défaut, je mettrais toujours une valeur de zéro (et l'appellerais `` Aucun '' ou `` Invalid '') qui n'est pas censé être utilisé, juste pour que si un membre de classe de cette énumération n'est pas initialisé correctement, la valeur non initialisée peut facilement être repérée, et dans les switchinstructions, elle passera à la defaultsection, où je lance un InvalidEnumArgumentException. Sinon, un programme peut involontairement continuer à s'exécuter avec la valeur zéro d'une énumération, qui peut être valide et passer inaperçue.
Allon Guralnek le
1
@Allon Il semble préférable de ne donner que les valeurs d'énumération dont vous savez qu'elles sont valides, puis de vérifier les valeurs invalides dans le setter et / ou le constructeur. De cette façon, vous savez immédiatement si un code ne fonctionne pas correctement plutôt que de permettre à un objet avec des données non valides d'exister pendant un temps inconnu et de le découvrir plus tard. À moins que «Aucun» ne représente un état valide, vous ne devez pas l'utiliser.
wprl
@SoloBold: Cela ressemble à un cas où vous n'oubliez pas d'initialiser un membre de classe enum dans un constructeur. Aucune quantité de validation ne vous aidera si vous oubliez d'initialiser ou de valider. En outre, il existe des classes DTO simples qui n'ont aucun constructeur, mais qui reposent plutôt sur des initialiseurs d'objets . Traquer un tel insecte peut être extrêmement douloureux. Néanmoins, l'ajout d'une valeur d'énumération inutilisée rend l'API laide. Je l'éviterais pour les API destinées à la consommation publique.
Allon Guralnek
@Allon C'est un bon point, mais je dirais que vous devriez valider les énumérations dans la fonction setter et lancer depuis le getter si le setter n'a jamais été appelé. De cette façon, vous avez un point de défaillance et vous pouvez planifier sur le terrain avec toujours une valeur valide, ce qui simplifie la conception et le code. Mes deux cents en tout cas.
wprl
@SoloBold: Imaginez une classe DTO avec 15 propriétés implémentées automatiquement. Son corps mesure 15 lignes. Imaginez maintenant cette même classe avec des propriétés régulières. C'est un minimum de 180 lignes avant d'ajouter une logique de vérification. Cette classe est utilisée exclusivement en interne à des fins de transfert de données uniquement. Lequel préférez-vous conserver, une classe de 15 lignes ou une classe de 180 lignes et plus? La succincte a sa valeur. Mais malgré tout, nos deux styles sont corrects, ils sont simplement différents. (Je suppose que c'est là que AOP entre et gagne les deux côtés de l'argument).
Allon Guralnek
66

Eh bien, je suppose que je ne suis pas d'accord avec la plupart des réponses qui disent de ne pas les numéroter explicitement. Je les numérote toujours explicitement, mais c'est parce que dans la plupart des cas, je finis par les conserver dans un flux de données où ils sont stockés sous forme de valeur entière. Si vous n'ajoutez pas explicitement les valeurs, puis ajoutez une nouvelle valeur, vous pouvez interrompre la sérialisation et ne pas pouvoir charger avec précision les anciens objets persistants. Si vous envisagez de faire un type de stockage persistant de ces valeurs, je vous recommande vivement de définir explicitement les valeurs.

pstrjds
la source
9
+1, d'accord, mais uniquement dans le cas où votre code repose sur l'entier pour une raison externe (par exemple, la sérialisation). Partout ailleurs, vous devriez vous en tenir à laisser le framework faire son travail. Si vous comptez sur les valeurs entières en interne, vous faites probablement quelque chose de mal (voir: laissez le framework faire son travail).
Matthew Scharley
3
J'aime persister le texte de l'énumération. Rend la base de données beaucoup plus utilisable imo.
Dave
2
Normalement, vous n'avez pas besoin de définir explicitement les valeurs ... même lorsqu'elles sont sérialisées. il suffit d'ajouter TOUJOURS de nouvelles valeurs à la fin. cela résoudra les problèmes de sérialisation. sinon, vous pourriez avoir besoin d'un versionnage de votre magasin de données (par exemple, un en-tête de fichier contenant une version pour changer le comportement lors de la lecture / écriture des valeurs) (ou voir le modèle memento)
Beachwalker
4
@Dave: À moins que vous ne documentiez explicitement que les textes d'énumération sont sacrés, vous vous mettez en échec si un futur programmeur décide d'ajuster un nom pour qu'il soit plus clair ou conforme à une convention de dénomination.
supercat du
1
@pstrjds: c'est un compromis stylistique je suppose - l'espace disque est bon marché cependant, le temps passé à convertir constamment entre les valeurs enum et un int lorsque la recherche dans la base de données est relativement chère (doublement si vous avez une configuration d'outil de rapport par rapport à une base de données ou quelque chose de similaire) . Si vous êtes inquiet de l'espace abotu, avec les nouvelles versions du serveur SQL, vous pouvez avoir une base de données compressée, ce qui signifie que 1000 occurrences de "SomeEnumTextualValue" utiliseront à peine plus d'espace. Bien sûr, cela ne fonctionnera pas pour tous les projets - c'est un compromis. Je pense que le souci de la bande passante sent peut-être l'optimisation prématurée!
Dave le
15

Un Enum est un type valeur et sa valeur par défaut (par exemple pour un champ Enum dans une classe) sera 0 si elle n'est pas initialisée explicitement.

Par conséquent, vous voulez généralement avoir 0 comme constante définie (par exemple, Inconnu).

Dans votre exemple, si vous voulez Inactiveêtre la valeur par défaut, il doit avoir la valeur zéro. Sinon, vous pouvez envisager d'ajouter une constante Unknown.

Certaines personnes ont recommandé de ne pas spécifier explicitement les valeurs de vos constantes. Probablement un bon conseil dans la plupart des cas, mais il y a des cas où vous voudrez le faire:

  • Énumérations des drapeaux

  • Énumérations dont les valeurs sont utilisées en interopérabilité avec des systèmes externes (par exemple COM).

Joe
la source
J'ai trouvé que les énumérations de drapeaux sont beaucoup plus lisibles lorsque vous ne définissez pas explicitement les valeurs. - Aussi beaucoup moins sujet aux erreurs pour laisser le compilateur faire le calcul binaire pour vous. (c'est-à [Flags] enum MyFlags { None = 0, A, B, Both = A | B, /* etc. */ }- dire est beaucoup plus lisible que [Flags] enum MyFlags { None = 0, A = 1, B = 2, Both = 3, /* etc */ }.)
BrainSlugs83
1
@ BrainSlugs83 - Je ne vois pas en quoi cela serait utile dans le cas général - par exemple, [Flags] enum MyFlags { None=0, A, B, C } cela entraînerait [Flags] enum MyFlags { None=0, A=1, B=2, C=3 }, alors que pour une énumération Flags, vous voudriez généralement C = 4.
Joe le
14

Sauf si vous avez une raison spécifique de le modifier, laissez les énumérations avec leurs valeurs par défaut, qui commencent à zéro.

public enum Status : byte
{
    Inactive,
    Active
}
PoissonPanierGordo
la source
6

Je dirais que la meilleure pratique est de ne pas les numéroter et de le laisser être implicite - ce qui commencerait à partir de 0. Puisque c'est implicite, c'est la préférence de langue qui est toujours bonne à suivre :)

John Humphreys - w00te
la source
6

Je commencerais une énumération de type booléen avec un 0.

Sauf si "Inatif" signifie autre chose que "Inactif" :)

Cela conserve la norme pour ceux-ci.

Mark Schultheiss
la source
6

Je dirais que cela dépend de la façon dont vous les utilisez. Pour marquer enum, il est recommandé d'avoir 0 pour Nonevaleur, comme ça:

[Flags]
enum MyEnum
{
    None = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    All = Option1 | Option2 | Option3,
}

Lorsque votre énumération est susceptible d'être mappée à une table de recherche de base de données, je la commencerais par 1. Cela ne devrait pas avoir beaucoup d'importance pour le code écrit par des professionnels, mais cela améliore la lisibilité.

Dans d'autres cas, je le laisserais tel quel, sans me soucier de savoir s'ils commencent par 0 ou 1.

Michael Sagalovich
la source
5

À moins que vous n'ayez une bonne raison d'utiliser les valeurs brutes, vous ne devriez jamais utiliser que des valeurs implicites et les référencer avec Status.Activeet Status.Inactive.

Le problème est que vous souhaiterez peut-être stocker des données dans un fichier plat ou une base de données, ou utiliser un fichier plat ou une base de données créé par quelqu'un d'autre. Si vous le faites vous-même, faites en sorte que la numérotation corresponde à l'utilisation de Enum.

Si les données ne vous appartiennent pas, bien sûr, vous voudrez utiliser tout ce que le développeur d'origine avait utilisé comme schéma de numérotation.

Si vous prévoyez d'utiliser Enum comme un ensemble d'indicateurs, il existe une convention simple qui vaut la peine d'être suivie:

enum Example
{
  None      = 0,            //  0
  Alpha     = 1 << 0,       //  1
  Beta      = 1 << 1,       //  2
  Gamma     = 1 << 2,       //  4
  Delta     = 1 << 3,       //  8
  Epsilon   = 1 << 4,       // 16
  All       = ~0,           // -1
  AlphaBeta = Alpha | Beta, //  3
}

Les valeurs doivent être des puissances de deux et peuvent être exprimées à l'aide d'opérations de décalage de bits. None, devrait évidemment être 0, mais Allest moins évident -1. ~0est la négation binaire de 0et donne un nombre dont chaque bit est défini sur 1, ce qui représente une valeur de-1 . Pour les indicateurs composés (souvent utilisés pour des raisons de commodité), d'autres valeurs peuvent être fusionnées à l'aide de l'opérateur binaire ou |.

zzzzBov
la source
3

N'attribuez aucun numéro. Utilisez-le simplement comme il est censé être utilisé.

Gnôle
la source
3

Si non spécifié, la numérotation commence à 0.

Il est important d'être explicite car les énumérations sont souvent sérialisées et stockées sous la forme d'un int, pas d'une chaîne.

Pour toute énumération stockée dans la base de données, nous numérotons toujours explicitement les options pour empêcher le décalage et la réaffectation pendant la maintenance.

Selon Microsoft, la convention recommandée est d'utiliser la première option zéro pour représenter une valeur par défaut non initialisée ou la plus courante.

Vous trouverez ci-dessous un raccourci pour commencer la numérotation à 1 au lieu de 0.

public enum Status : byte
{
    Inactive = 1,
    Active
}

Si vous souhaitez définir des valeurs d'indicateur afin d'utiliser des opérateurs de bits sur les valeurs d'énumération, ne commencez pas la numérotation à la valeur zéro.

dru
la source
2

Si vous commencez à 1, vous pouvez facilement compter vos affaires.

{
    BOX_THING1     = 1,
    BOX_THING2     = 2,
    BOX_NUM_THING  = BOX_THING2
};

Si vous commencez à 0, utilisez le premier comme valeur pour les choses non initialisées.

{
    BOX_NO_THING   = 0,
    BOX_THING1     = 1,
    BOX_THING2     = 2,
    BOX_NUM_THING  = BOX_THING2
};
Jonathan Cline IEEE
la source
5
Désolé, Jonathan. Je pense que cette suggestion est un peu "old school" dans mon esprit (le genre de convention vient des années de bas niveau-c). C'est une solution rapide pour "intégrer" des informations supplémentaires sur l'énumération, mais ce n'est pas une bonne pratique dans les systèmes plus importants. Vous ne devriez pas utiliser une énumération si vous avez besoin d'informations sur le nombre de valeurs disponibles, etc. Et qu'en est-il d'un BOX_NO_THING1? Lui donnerais-tu BOX_NO_THING + 1? Les énumérations doivent être utilisées comme elles sont censées être utilisées: des valeurs (int) spécifiques représentées par des noms «parlants».
Beachwalker
Hm. Vous supposez que c'est de la vieille école parce que j'ai utilisé toutes les majuscules, je suppose, plutôt que MicrosoftBumpyCaseWithLongNames. Bien que je sois d'accord, il vaut mieux utiliser des itérateurs que des boucles jusqu'à atteindre une définition XyzNumDefsInMyEnum énumérée.
Jonathan Cline IEEE
C'est une pratique terrible en C # de différentes manières. Maintenant, lorsque vous allez obtenir un décompte de vos énumérations de la bonne façon, ou si vous essayez de les énumérer de la bonne façon, vous obtiendrez un objet supplémentaire en double. De plus, cela rend l'appel .ToString () potentiellement ambigu (falsifiant la sérialisation moderne), entre autres.
BrainSlugs83
0

Tout d'abord, à moins que vous ne spécifiiez des valeurs spécifiques pour une raison (la valeur numérique a un sens ailleurs, c'est-à-dire la base de données ou un service externe), ne spécifiez pas du tout de valeurs numériques et laissez-les être explicites.

Deuxièmement, vous devriez toujours avoir un élément de valeur zéro (dans les énumérations non-flags). Cet élément sera utilisé comme valeur par défaut.

Justin Niessner
la source
0

Ne les démarrez pas à 0 sauf s'il y a une raison de le faire, comme les utiliser comme index dans un tableau ou une liste, ou s'il y a une autre raison pratique (comme les utiliser dans des opérations au niveau du bit).

Votre enumdevrait commencer exactement là où il le faut. Cela n'a pas besoin non plus d'être séquentiel. Les valeurs, si elles sont explicitement définies, doivent refléter une signification sémantique ou une considération pratique. Par exemple, un enumdes "bouteilles sur le mur" doit être numéroté de 1 à 99, tandis qu'un enumpour les puissances de 4 devrait probablement commencer à 4 et continuer avec 16, 64, 256, etc.

De plus, l'ajout d'un élément de valeur zéro au enumne doit être effectué que s'il représente un état valide. Parfois, «aucune», «inconnue», «manquante», etc. sont des valeurs valides, mais souvent elles ne le sont pas.

wprl
la source
-1

J'aime commencer mes énumérations à 0, car c'est la valeur par défaut, mais j'aime aussi inclure une valeur inconnue, avec une valeur de -1. Cela devient alors la valeur par défaut et peut parfois aider au débogage.

Tomas McGuinness
la source
4
Idée horrible. En tant que type valeur, les énumérations sont toujours initialisées à zéro. Si vous voulez avoir une valeur qui représente inconnue ou non initialisée, elle doit être 0. Vous ne pouvez pas changer la valeur par défaut à -1, le remplissage à zéro est codé en dur dans tout le CLR.
Ben Voigt
Ah, je ne m'en suis pas rendu compte. Je règle généralement la valeur d'une valeur / propriété d'énumération lorsque je décalère / initialise. Merci pour le pointeur.
Tomas McGuinness