Чи повинен Enum починати з 0 або 1?


136

Уявіть, що я визначив наступного Enum:

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

Яка найкраща практика використовувати enum? Чи слід починати з 1подібного прикладу чи починати з 0(без явних значень) так:

public enum Status : byte
{
    Inactive,
    Active
}

13
Вам справді взагалі потрібно чітко їх нумерувати?
Юк

164
Енуми були створені саме для того, щоб подібні речі не були важливими.
BoltClock

9
@Daniel - arrgh ні! Краще використовувати enum, коли ти думаєш, що буде робити булевий, ніж використовувати boolean, коли ти думаєш про перерахунок.
ААТ

22
@Daniel через значення FileNotFound, звичайно
Joubarc

5
xkcd.com/163 застосовується до enum навіть краще, ніж це для масивів індексів.
Ліворуч близько

Відповіді:


161

Рамкові рекомендації щодо дизайну :

✔️ DO забезпечити значення нуля на простих перерахунках.

Подумайте, як називати значення чимось на зразок "None". Якщо таке значення не підходить для цього конкретного перерахунку, найпоширенішому за замовчуванням для enum слід призначити базове значення нульове.

Рамкові керівні принципи / Проектування прапорців :

❌ Уникнути використання нульових значень enum значень нуля, якщо значення не являє "очищені всі прапори" та не названо відповідним чином, як це визначено в наступному керівництві.

✔️ НАЗВАЙТЕ нульове значення, якщо прапор перераховується None. Для enum прапора значення завжди повинно означати "всі прапори очищені".


28
Невдача рано: Якщо "None" не підходить, але немає логічного значення за замовчуванням, я все одно поставив би значення нуля (і називатиме його "None" або "Invalid"), яке не призначене для використання, просто так якщо член класу цього перерахунку не ініціалізується належним чином, неініціалізоване значення може бути легко помічено, і у switchвисловлюваннях воно перейде до defaultрозділу, куди я кидаю InvalidEnumArgumentException. В іншому випадку програма може ненавмисно продовжувати працювати з нульовим значенням перерахунку, яке може бути дійсним і залишатися непоміченим.
Аллон Гуралнек

1
@Allon Здається, краще вказати лише значення enum, які ви знаєте, що є дійсними, а потім перевірити наявність недійсних значень у сеттері та / або конструкторі. Таким чином ви відразу дізнаєтесь, що якийсь код працює неправильно, а не дозволяє об'єкту з недійсними даними існувати якийсь невідомий час та дізнаватися про нього пізніше. Якщо "None" не є дійсним станом, використовувати його не слід.
wprl

@SoloBold: Це звучить як випадок, коли ви не забудете ініціалізувати члена класу enum у конструкторі. Жодна сума перевірки не допоможе вам, якщо ви забудете ініціалізувати або забудете перевірити. Крім того, існують прості класи DTO, які не мають конструкторів, але замість цього покладаються на ініціалізатори об'єктів . Полювання на такого клопа може бути надзвичайно болючим. Тим не менш, додавання невикористаного значення перерахування створює некрасивий API. Я б цього не уникав для API, орієнтованого на суспільне споживання.
Аллон Гуралнек

@Allon Це хороший момент, але я б заперечував, що вам слід перевірити перерахунки функції сетера та викидати з getter, якщо сеттер ніколи не викликався. Таким чином у вас є одна точка відмови, і ви можете планувати на полі завжди мати дійсне значення, що спрощує дизайн та код. Мої два центи все одно.
wprl

@SoloBold: Уявіть клас DTO з 15 властивостями, реалізованими автоматично. Її тіло довжиною 15 ліній. А тепер уявіть той самий клас із регулярними властивостями. Це мінімум 180 рядків, перш ніж додавати будь-яку логіку перевірки. Цей клас використовується виключно внутрішньо лише для передачі даних. Який би ви хотіли підтримувати, клас 15 ліній або клас 180 ліній? Успішність має своє значення. Але все-таки обидва наші стилі правильні, вони просто різні. (Я здогадуюсь, що саме тут AOP прокотиться і виграє обидві сторони аргументу).
Аллон Гуралнек

66

Ну, я думаю, я не погоджуюся з більшістю відповідей, які говорять про те, щоб явно їх не нумерувати. Я завжди чітко їх нумерую, але це тому, що в більшості випадків я в кінцевому підсумку зберігаю їх у потоці даних, де вони зберігаються як ціле значення. Якщо явно не додати значення, а потім додати нове значення, ви можете порушити серіалізацію, а потім не зможете точно завантажити старі збережені об'єкти. Якщо ви збираєтеся робити будь-який тип постійного зберігання цих значень, я б дуже рекомендував чітко встановити ці значення.


9
+1, узгоджено, але лише у тому випадку, коли ваш код з якихось зовнішніх причин покладається на ціле число (наприклад, серіалізація). Скрізь, де ви повинні дотримуватися, просто дозволяючи рамці робити свою роботу. Якщо ви покладаєтесь на цілочисельні значення внутрішньо, то, ймовірно, ви робите щось не так (див.: Нехай рамка виконує свою роботу).
Меттью Шарлі

3
Мені подобається наполягати текст перерахунку. Робить базу даних набагато більш корисним IMO.
Дейв

2
Зазвичай вам не потрібно явно встановлювати значення ... навіть коли вони серіалізуються. просто ЗАВЖДИ додайте нові значення наприкінці. це вирішить проблеми серіалізації. інакше вам може знадобитися версія версії вашого сховища даних (наприклад, заголовок файлу, що містить версію для зміни поведінки під час читання / запису значень) (або див. шаблон
пам’яті

4
@Dave: Якщо ви не будете експліцитно документувати, що тексти перерахувань є священними, ви налаштовуєте себе на відмову, якщо майбутній програміст вирішить скоригувати ім’я, щоб бути зрозумілішим або відповідати деяким умовам іменування.
supercat

1
@pstrjds: мабуть, це стилістична компроміс - дисковий простір дешевий, але час, витрачений на постійне перетворення значень enum та внутрішній пошук на db, є відносно дорогим (удвічі, якщо у вас є інструмент для створення звітів у базі даних чи щось подібне) . Якщо ви турбуєтесь у просторі abbotu, з новішими версіями сервера SQL ви можете стиснути базу даних, що означає, що 1000 випадків "SomeEnumTextualValue" будуть використовувати майже більше місця. Звичайно, це працюватиме не для всіх проектів - це компроміс. Я думаю, що турбота про пропускну здатність пахне передчасною оптимізацією, можливо!
Дейв

15

Enum - це тип значення, і його значення за замовчуванням (наприклад, для поля Enum у класі) буде 0, якщо не буде ініціалізовано явно.

Тому, як правило, ви хочете мати 0 як визначену константу (наприклад, невідомо).

У вашому прикладі, якщо ви хочете Inactiveбути типовим, то воно повинно мати значення нуль. В іншому випадку ви можете розглянути можливість додавання константи Unknown.

Деякі люди рекомендують не чітко вказувати значення для своїх констант. Мабуть, хороша порада в більшості випадків, але є деякі випадки, коли вам захочеться це зробити:

  • Прапори перераховують

  • Енуми, значення яких використовуються в взаємодії з зовнішніми системами (наприклад, COM).


Я виявив, що переліки прапорів набагато легше читаються, коли ви чітко не встановлюєте значення. - Крім того, менш схильні помилки, щоб дозволити компілятору робити двійкову математику за вас. (тобто [Flags] enum MyFlags { None = 0, A, B, Both = A | B, /* etc. */ }є способом легше читати, ніж [Flags] enum MyFlags { None = 0, A = 1, B = 2, Both = 3, /* etc */ }.)
BrainSlugs83

1
@ BrainSlugs83 - Я не бачу, як це було б корисно в загальному випадку - наприклад, [Flags] enum MyFlags { None=0, A, B, C } це призведе до [Flags] enum MyFlags { None=0, A=1, B=2, C=3 }, тоді як для переліку прапорів ти зазвичай хочеш C = 4.
Джо

14

Якщо у вас немає конкретної причини змінити його, залиште перерахунки зі своїми значеннями за замовчуванням, які починаються з нуля.

public enum Status : byte
{
    Inactive,
    Active
}

6

Я б сказав, що найкраща практика полягає в тому, щоб не нумерувати їх, а дозволити це неявно - що починається з 0. Оскільки це неявне, мовні переваги, яких завжди добре слідувати :)


6

Я би розпочав перерахунок булевого типу з 0.

Якщо тільки "Неактивний" означає щось інше, ніж "Неактивний" :)

Це зберігає стандарт для тих.


6

Я б сказав, це залежить від того, як ти їх використовуєш. Для позначення переліків переліком є ​​хорошою практикою мати Noneзначення 0 , наприклад:

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

Коли ваш перерахунок, ймовірно, буде відображений у таблиці пошуку бази даних, я б почав його з 1. Це не має великого значення для професійно написаного коду, але це покращує читабельність.

В інших випадках я залишаю це так, як є, не хвилюючись, починають вони з 0 або 1.


5

Якщо у вас немає вагомих причин використовувати необроблені значення, ви повинні завжди використовувати лише неявні значення та посилатись на них з Status.Activeта Status.Inactive.

Суть полягає в тому, що ви, можливо, захочете зберігати дані у плоському файлі чи БД, або використовувати плоский файл або БД, які створив хтось інший. Якщо ви робите це самостійно, зробіть так, щоб нумерація відповідала тому, для чого використовується Enum.

Якщо дані не ваші, звичайно, ви хочете використовувати все те, що оригінальний розробник використовував як схему нумерації.

Якщо ви плануєте використовувати Enum як набір прапорів, існує проста конвенція, якої варто дотримуватися:

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
}

Значення мають бути двома потужностями і можуть бути виражені за допомогою операцій зсуву бітів. None, очевидно, має бути 0, але Allменш очевидно -1. ~0є двійковим запереченням 0і приводить до числа, для якого встановлено кожен біт 1, який представляє значення-1 . Для складених прапорів (які часто використовуються для зручності) інші значення можуть бути об'єднані за допомогою бітового або оператора |.


3

Не призначайте жодних чисел. Просто використовуйте його так, як слід було використовувати.


3

Якщо не вказано, нумерація починається з 0.

Важливо бути явним, оскільки переписки часто серіалізуються і зберігаються як int, а не рядок.

Для будь-якого перерахунку, що зберігається в базі даних, ми завжди чітко нумеруємо параметри, щоб запобігти зміщенню та перепризначення під час обслуговування.

На думку Microsoft, рекомендована конвенція полягає у використанні першої нульової опції для представлення неініціалізованого або найбільш поширеного значення за замовчуванням.

Нижче наведено ярлик для початку нумерації з 1 замість 0.

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

Якщо ви хочете встановити значення прапорців, щоб використовувати бітові оператори для значень перерахувань, не починайте нумерацію з нульового значення.


2

Якщо ви почнете з 1, ви зможете легко отримати кількість своїх речей.

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

Якщо ви починаєте з 0, то використовуйте перший як значення для неініціалізованих речей.

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

5
Вибач, Джонатане. Я думаю, ця пропозиція є трохи "старою школою" на мою думку (вид конвенції походить з років низького рівня). Це нормально, як швидке рішення "вбудувати" деякі додаткові відомості про перерахунок, але це не є хорошою практикою у великих системах. Ви не повинні використовувати enum, якщо вам потрібна інформація про кількість доступних значень тощо. А як щодо BOX_NO_THING1? Ви б дали йому BOX_NO_THING + 1? Енуми повинні використовуватися як те, що вони повинні бути використані для: конкретних (int) значень, представлених "говорячими" іменами.
Beachwalker

Гм. Ви припускаєте, що це стара школа, тому що я використовував усі шапки, а не MicrosoftBumpyCaseWithLongNames. Хоча я згоден, краще використовувати ітератори, ніж циклічні, поки не досягнемо переліченого визначення XyzNumDefsInMyEnum.
Джонатан Клайн IEEE

Це жахлива практика в C # різними способами. Тепер, коли ви перейдете до підрахунку ваших перелічених людей правильним способом, або якщо ви спробуєте перерахувати їх правильним шляхом, ви отримаєте додатковий, повторюваний об’єкт. Крім того, це робить виклик .ToString () потенційно неоднозначним (накручуючи сучасну серіалізацію), серед іншого.
BrainSlugs83

0

Перш за все, якщо ви не вказуєте конкретні значення з причини (числове значення має значення десь в іншому місці, тобто База даних або зовнішня служба), тоді взагалі не вказуйте числові значення і нехай вони будуть явними.

По-друге, у вас завжди повинен бути елемент з нульовим значенням (в перерахунках без прапорців). Цей елемент буде використовуватися як значення за замовчуванням.


0

Не запускайте їх з 0, якщо немає причин, наприклад, використовувати їх як індекси до масиву чи списку, або якщо є якась інша практична причина (наприклад, використання їх у побітових операціях).

Ви enumповинні почати саме там , де це необхідно. Він також не повинен бути послідовним. Значення, якщо вони прямо встановлені, повинні відображати певний смисловий сенс або практичний розгляд. Наприклад, enum"пляшки на стіні" повинні бути пронумеровані від 1 до 99, тоді як enumдля потужностей 4, мабуть, слід починати з 4 і продовжувати з 16, 64, 256 і т.д.

Крім того, додавання до нульового елемента елемента enumслід робити, лише якщо він є дійсним станом. Іноді "жоден", "невідомий", "відсутній" тощо є дійсними значеннями, але багато разів вони не є.


-1

Мені подобається запускати перерахунки на 0, оскільки це за замовчуванням, але я також хочу включити значення Невідомо зі значенням -1. Потім це стає за замовчуванням і може допомогти при налагодженні іноді.


4
Жахлива ідея. Як тип значення, перерахунки завжди ініціалізуються до нуля. Якщо у вас буде якесь значення, яке представляє невідоме або неініціалізоване, воно повинно бути 0. Ви не можете змінити типове значення на -1, нульове заповнення жорстко кодується в усьому CLR.
Бен Войгт

Ах, я цього не усвідомлював. Я, як правило, встановлюю значення значення / властивості enum, коли я деколюю / ініціалізую. Дякуємо за вказівник.
Tomas McGuinness
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.