Czy Enum powinno zaczynać się od 0 czy 1?

136

Wyobraź sobie, że zdefiniowałem następujący Enum:

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

Jaka jest najlepsza praktyka korzystania z wyliczenia? Czy powinien zaczynać się 1od powyższego przykładu, czy od 0(bez jawnych wartości) w następujący sposób:

public enum Status : byte
{
    Inactive,
    Active
}
Acaz Souza
źródło
13
Czy naprawdę musisz je w ogóle ponumerować?
Fuj
164
Wyliczenia zostały stworzone tylko po to, aby takie rzeczy nie były ważne.
BoltClock
9
@Daniel - arrgh no! Lepiej jest użyć wyliczenia, gdy myślisz , że wystarczy wyliczenie, niż użyć wartości logicznej, gdy myślisz o wyliczeniu.
AAT
22
@Daniel z powodu oczywiście wartości FileNotFound
Joubarc
5
xkcd.com/163 stosuje się do wyliczenia nawet lepiej niż do indeksów tablicowych.
leftaround około

Odpowiedzi:

161

Wytyczne dotyczące projektowania ram :

✔️ PODAJ wartość zero w przypadku prostych wyliczeń.

Rozważ nazwanie wartości czymś w rodzaju „Brak”. Jeśli taka wartość nie jest odpowiednia dla tego konkretnego wyliczenia, najczęściej używana wartość domyślna wyliczenia powinna mieć wartość bazową zero.

Wytyczne dotyczące projektowania ram / projektowanie wyliczeń flag :

❌ UNIKAJ używania wartości wyliczenia flag o wartości zero, chyba że wartość reprezentuje „wszystkie flagi są wyczyszczone” i ma odpowiednią nazwę, zgodnie z następnymi wytycznymi.

✔️ NALEŻY nazwać zerową wartość wyliczeń flag Brak. W przypadku wyliczenia flag wartość musi zawsze oznaczać „wszystkie flagi są wyczyszczone”.

Andrey Taptunov
źródło
28
Niepowodzenie wcześnie: jeśli „Brak” nie jest odpowiedni, ale nie ma logicznej wartości domyślnej, nadal wstawiłbym wartość zero (i nazwałbym ją „Brak” lub „Nieprawidłowy”), która nie jest przeznaczona do użycia, tylko po to jeśli element członkowski klasy tego wyliczenia nie zostanie poprawnie zainicjowany, niezainicjowaną wartość można łatwo zauważyć, aw switchinstrukcjach przeskoczy do defaultsekcji, w której rzucam InvalidEnumArgumentException. W przeciwnym razie program może niechcący kontynuować działanie z zerową wartością wyliczenia, która może być poprawna i niezauważona.
Allon Guralnek
1
@Allon Wydaje się, że lepiej jest podać tylko wartości wyliczenia, o których wiesz, że są prawidłowe, a następnie sprawdzić, czy w metodzie ustawiającej i / lub konstruktorze nie występują nieprawidłowe wartości. W ten sposób od razu wiesz, że jakiś kod nie działa poprawnie, zamiast pozwalać obiektowi z nieprawidłowymi danymi istnieć przez nieznany czas i dowiedzieć się o tym później. O ile „Brak” nie reprezentuje prawidłowego stanu, nie należy go używać.
wprl,
@SoloBold: To brzmi jak przypadek, w którym nie zapomnisz zainicjować elementu członkowskiego klasy enum w konstruktorze. Żadna ilość walidacji nie pomoże, jeśli zapomnisz o inicjalizacji lub zapomnisz o sprawdzeniu poprawności. Istnieją również proste klasy DTO, które nie mają żadnych konstruktorów, ale zamiast tego polegają na inicjatorach obiektów . Polowanie na takiego robaka może być niezwykle bolesne. Niemniej jednak dodanie nieużywanej wartości wyliczenia powoduje brzydki interfejs API. Unikałbym tego w przypadku interfejsów API przeznaczonych do użytku publicznego.
Allon Guralnek
@Allon To dobra uwaga, ale uważam, że powinieneś sprawdzać wyliczenia w funkcji ustawiającej i wyrzucać z metody pobierającej, jeśli setter nigdy nie został wywołany. W ten sposób masz jeden punkt awarii i możesz zaplanować, że pole zawsze będzie miało poprawną wartość, co upraszcza projekt i kod. W każdym razie moje dwa centy.
wprl,
@SoloBold: Wyobraź sobie klasę DTO z 15 automatycznie implementowanymi właściwościami. Jego ciało ma 15 linii długości. Teraz wyobraź sobie tę samą klasę o zwykłych właściwościach. To minimum 180 linii przed dodaniem logiki weryfikacji. Ta klasa jest używana wyłącznie wewnętrznie, wyłącznie do celów przesyłania danych. Którą wolisz zachować, klasę 15 linii czy klasę 180+ linii? Zwięzłość ma swoją wartość. Mimo to oba nasze style są poprawne, są po prostu różne. (Myślę, że w tym miejscu AOP wkracza i wygrywa obie strony sporu).
Allon Guralnek
66

Cóż, myślę, że nie zgadzam się z większością odpowiedzi, które mówią, że nie należy ich wyraźnie numerować. Zawsze jawnie je numeruję, ale dzieje się tak dlatego, że w większości przypadków ostatecznie utrwalam je w strumieniu danych, gdzie są przechowywane jako wartości całkowite. Jeśli nie dodasz jawnie wartości, a następnie dodasz nową wartość, możesz przerwać serializację, a następnie nie będzie można dokładnie załadować starych utrwalonych obiektów. Jeśli masz zamiar dokonać jakiegokolwiek trwałego przechowywania tych wartości, zdecydowanie polecam jawne ustawienie wartości.

pstrjds
źródło
9
+1, uzgodnione, ale tylko w przypadku, gdy Twój kod opiera się na liczbie całkowitej z jakiegoś zewnętrznego powodu (np. Serializacja). Wszędzie indziej powinieneś po prostu pozwolić, aby framework wykonał swoją pracę. Jeśli wewnętrznie polegasz na wartościach całkowitych, prawdopodobnie robisz coś nie tak (zobacz: niech framework wykona swoją pracę).
Matthew Scharley
3
Lubię utrwalać tekst wyliczenia. Sprawia, że ​​baza danych jest znacznie bardziej użyteczna imo.
Dave
2
Zwykle nie trzeba jawnie ustawiać wartości ... nawet wtedy, gdy są serializowane. po prostu zrób ZAWSZE dodawanie nowych wartości na końcu. rozwiąże to problemy serializacji. w przeciwnym razie możesz potrzebować wersji swojego magazynu danych (np. nagłówek pliku zawierający wersję do zmiany zachowania podczas odczytywania / zapisywania wartości) (lub zobacz wzorzec memento)
Beachwalker
4
@Dave: O ile wyraźnie nie udokumentujesz, że teksty wyliczenia są święte, narażasz się na niepowodzenie, jeśli przyszły programista zdecyduje się dostosować nazwę, aby była bardziej przejrzysta lub zgodna z jakąś konwencją nazewnictwa.
supercat
1
@pstrjds: wydaje mi się, że to stylistyczny kompromis - miejsce na dysku jest jednak tanie, czas spędzony na ciągłej konwersji między wartościami wyliczeniowymi i int podczas wyszukiwania bazy danych jest stosunkowo drogi (podwójnie, więc jeśli masz konfigurację narzędzia do raportowania w bazie danych lub czymś podobnym) . Jeśli martwisz się o przestrzeń, z nowszymi wersjami serwera SQL możesz skompresować bazę danych, co oznacza, że ​​1000 wystąpień „SomeEnumTextualValue” zajmie niewiele więcej miejsca. Oczywiście to nie zadziała dla wszystkich projektów - to kompromis. Myślę, że obawa o przepustowość może pachnieć jak przedwczesna optymalizacja!
Dave
15

Enum jest typem wartości, a jego wartość domyślna (na przykład dla pola Enum w klasie) będzie wynosić 0, jeśli nie zostanie zainicjowana jawnie.

Dlatego generalnie chcesz mieć 0 jako zdefiniowaną stałą (np. Nieznane).

W twoim przykładzie, jeśli chcesz Inactivebyć wartością domyślną, powinna mieć wartość zero. W przeciwnym razie warto rozważyć dodanie stałej Unknown.

Niektórzy zalecają, aby nie podawać jawnie wartości stałych. Prawdopodobnie w większości przypadków dobra rada, ale w niektórych przypadkach będziesz chciał to zrobić:

  • Flagi wyliczenia

  • Wyliczenia, których wartości są używane we współpracy z systemami zewnętrznymi (np. COM).

Joe
źródło
Odkryłem, że wyliczenia flag są o wiele bardziej czytelne, jeśli nie ustawisz jawnie wartości. - Również znacznie mniej podatny na błędy, aby kompilator wykonał binarną matematykę za Ciebie. (tj. [Flags] enum MyFlags { None = 0, A, B, Both = A | B, /* etc. */ }jest znacznie bardziej czytelny niż [Flags] enum MyFlags { None = 0, A = 1, B = 2, Both = 3, /* etc */ }.)
BrainSlugs83
1
@ BrainSlugs83 - Nie widzę jak to będzie pomocne w ogólnym przypadku - np [Flags] enum MyFlags { None=0, A, B, C } spowodowałoby [Flags] enum MyFlags { None=0, A=1, B=2, C=3 }, podczas gdy dla Flagi ENUM byś zazwyczaj chcą C = 4.
Joe
14

Jeśli nie masz konkretnego powodu, aby to zmienić, pozostaw wyliczenia z ich domyślnymi wartościami, które zaczynają się od zera.

public enum Status : byte
{
    Inactive,
    Active
}
FishBasketGordo
źródło
6

Powiedziałbym, że najlepszą praktyką jest nie numerowanie ich i pozostawienie ich w domyśle - co zacznie się od 0. Ponieważ jest to niejawne, to preferencja językowa, którą zawsze dobrze jest przestrzegać :)

John Humphreys - w00te
źródło
6

Zacząłbym wyliczenie typu boolowskiego od 0.

Chyba że „Nieaktywny” oznacza coś innego niż „Nieaktywny” :)

To zachowuje standard dla tych.

Mark Schultheiss
źródło
6

Powiedziałbym, że to zależy od tego, jak ich używasz. Przy oznaczaniu wyliczenia dobrą praktyką jest przyjmowanie Nonewartości 0 dla wartości, na przykład:

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

Gdy prawdopodobnie wyliczenie zostanie zmapowane do tabeli wyszukiwania bazy danych, zacznę od 1. Nie powinno to mieć większego znaczenia dla profesjonalnie napisanego kodu, ale poprawia to czytelność.

W innych przypadkach zostawiłbym to tak, jak jest, nie dbając o to, czy zaczynają się od 0, czy 1.

Michael Sagalovich
źródło
5

O ile nie masz dobrego powodu, aby używać surowych wartości, powinieneś zawsze używać niejawnych wartości i odwoływać się do nich za pomocą Status.Activei Status.Inactive.

Problem polega na tym, że możesz chcieć przechowywać dane w pliku prostym lub bazie danych albo użyć pliku płaskiego lub bazy danych utworzonej przez kogoś innego. Jeśli robisz to sam, zrób to tak, aby numeracja była zgodna z tym, do czego służy Enum.

Jeśli dane nie są twoje, oczywiście będziesz chciał użyć tego, co pierwotny programista użył jako schematu numeracji.

Jeśli planujesz używać Enum jako zestawu flag, istnieje prosta konwencja, której warto przestrzegać:

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
}

Wartości powinny być potęgami dwójki i mogą być wyrażone przy użyciu operacji przesunięcia bitowego. Noneoczywiście powinno 0, ale Alljest to mniej oczywiste -1. ~0jest binarną negacją 0i skutkuje liczbą, dla której każdy bit jest ustawiony na 1, który reprezentuje wartość-1 . W przypadku flag złożonych (często używanych dla wygody) inne wartości mogą być łączone przy użyciu operatora bitowego lub |.

zzzzBov
źródło
3

Nie przypisuj żadnych numerów. Po prostu używaj go tak, jak powinien być używany.

Samogon
źródło
3

Jeśli nie zostanie określony, numeracja zaczyna się od 0.

Ważne jest, aby być jawnym, ponieważ wyliczenia są często serializowane i przechowywane jako int, a nie ciąg.

Dla każdego wyliczenia przechowywanego w bazie danych zawsze jawnie numerujemy opcje, aby zapobiec przenoszeniu i ponownemu przypisywaniu podczas konserwacji.

Według firmy Microsoft zalecaną konwencją jest użycie pierwszej opcji zerowej do reprezentowania niezainicjowanej lub najczęściej używanej wartości domyślnej.

Poniżej znajduje się skrót do rozpoczynania numeracji od 1 zamiast 0.

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

Jeśli chcesz ustawić wartości flag w celu użycia operatorów bitowych na wartościach wyliczeniowych, nie rozpoczynaj numerowania od wartości zerowej.

dru
źródło
2

Jeśli zaczniesz od 1, możesz łatwo policzyć swoje rzeczy.

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

Jeśli zaczniesz od 0, użyj pierwszej jako wartości dla rzeczy niezainicjowanych.

{
    BOX_NO_THING   = 0,
    BOX_THING1     = 1,
    BOX_THING2     = 2,
    BOX_NUM_THING  = BOX_THING2
};
Jonathan Cline IEEE
źródło
5
Przepraszam, Jonathan. Myślę, że ta sugestia jest trochę „oldschoolowa” w moim umyśle (rodzaj konwencji pochodzi z lat niskiego poziomu). Jest to w porządku, jako szybkie rozwiązanie pozwalające „osadzić” dodatkowe informacje o wyliczeniu, ale nie jest to dobra praktyka w większych systemach. Nie powinieneś używać wyliczenia, jeśli potrzebujesz informacji o liczbie dostępnych wartości itp. A co z BOX_NO_THING1? Czy dasz mu BOX_NO_THING + 1? Wyliczenia powinny być używane zgodnie z ich przeznaczeniem: Określone (int) wartości reprezentowane przez „mówiące” nazwy.
Beachwalker
Hm. Zakładasz, że to stara szkoła, ponieważ chyba użyłem wszystkich wielkich liter zamiast MicrosoftBumpyCaseWithLongNames. Chociaż zgadzam się, lepiej jest używać iteratorów niż pętli, aż do osiągnięcia wyliczonej definicji XyzNumDefsInMyEnum.
Jonathan Cline IEEE
To straszna praktyka w C # na wiele sposobów. Teraz, gdy idziesz, aby policzyć swoje wyliczenia we właściwy sposób lub jeśli spróbujesz wyliczyć je we właściwy sposób, otrzymasz dodatkowy, zduplikowany obiekt. Ponadto, między innymi, powoduje to, że wywołanie .ToString () jest potencjalnie niejednoznaczne (spieprzenie nowoczesnej serializacji).
BrainSlugs83
0

Po pierwsze, jeśli nie określasz konkretnych wartości z jakiegoś powodu (wartość liczbowa ma znaczenie gdzie indziej, np. Baza danych lub usługa zewnętrzna), nie określaj w ogóle wartości liczbowych i pozwól im być jawne.

Po drugie, zawsze powinieneś mieć element o wartości zerowej (w wyliczeniach bez flag). Ten element będzie używany jako wartość domyślna.

Justin Niessner
źródło
0

Nie rozpoczynaj ich od 0, chyba że jest ku temu powód, na przykład używanie ich jako indeksów do tablicy lub listy, lub jeśli istnieje inny praktyczny powód (np. Używanie ich w operacjach bitowych).

Twój enumpowinien zacząć dokładnie tam, gdzie powinien. Nie musi też być sekwencyjny. Wartości, jeśli są wyraźnie określone, muszą odzwierciedlać pewne znaczenie semantyczne lub praktyczne rozważania. Na przykład enum„butelki na ścianie” powinny być ponumerowane od 1 do 99, podczas gdy enumdla potęg 4 powinno się prawdopodobnie zaczynać od 4 i kontynuować od 16, 64, 256 itd.

Ponadto dodanie elementu o wartości zerowej do elementu enumnależy wykonać tylko wtedy, gdy reprezentuje prawidłowy stan. Czasami „brak”, „nieznany”, „brak” itp. Są prawidłowymi wartościami, ale często tak nie jest.

wprl
źródło
-1

Lubię zaczynać wyliczenia od 0, ponieważ jest to ustawienie domyślne, ale lubię też dodawać nieznaną wartość o wartości -1. To wtedy staje się domyślne i może czasami pomóc w debugowaniu.

Tomas McGuinness
źródło
4
Okropny pomysł. Jako typ wartości wyliczenia są zawsze inicjowane na zero. Jeśli masz mieć jakąś wartość, która reprezentuje nieznaną lub niezainicjowaną, musi być 0. Nie możesz zmienić wartości domyślnej na -1, wypełnianie zerami jest na stałe zakodowane w całym CLR.
Ben Voigt
Ach, nie zdawałem sobie z tego sprawy. Generalnie ustawiam wartość wartości / właściwości wyliczenia, gdy dekaluję / inicjuję. Dzięki za wskazówkę.
Tomas McGuinness