154

Imagine I have defined the following Enum:

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

What's the best practice to use enum? Should it start with 1 like the above example or start with 0 (without the explicit values) like this:

public enum Status : byte
{
    Inactive,
    Active
}
Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
Acaz Souza
  • 8,311
  • 11
  • 54
  • 97
  • 14
    Do you really need to number them explicitly at all? – Yuck Aug 31 '11 at 13:16
  • 169
    Enums were created just so that things like this wouldn't be important. – BoltClock Aug 31 '11 at 13:17
  • 9
    @Daniel -- arrgh no! Better to use an enum when you **think** a boolean will do than to use a boolean when you are thinking of an enum. – AAT Aug 31 '11 at 14:09
  • 22
    @Daniel because of the FileNotFound value, of course – Joubarc Aug 31 '11 at 14:13
  • 1
    @AAT: http://stackoverflow.com/questions/1240739/boolean-parameters-do-they-smell/ – Adam Robinson Aug 31 '11 at 14:21
  • @Adam - I agree that question is related to this issue (although it's specifically about boolean function parameters) but I think there are also strong arguments against using boolean for members / variables in many cases. There **are** things which genuinely only have 2 states and will never have a 3rd, but once you get from the "real world" into the software domain they are actually quite rare, except for computed conditions (for which booleans are of course exactly right). – AAT Aug 31 '11 at 16:02
  • 3
    @Daniel: Type safety is another consideration. – Thomas Eding Aug 31 '11 at 16:40
  • 7
    http://xkcd.com/163/ applies to enum even better than it does to array indices. – leftaroundabout Aug 31 '11 at 18:43
  • That XKCD applies to this in now way :) It's pretty nonsensical... – wprl Sep 07 '11 at 17:54
  • @DanielA.White you should never use a `boolean` for something that is not inherently a "yes/no" question, even if it only has two possible values. Aside from considering that he may add another Status next week called "Pending", etc., it just makes the code more readable and understandable for humans. -- (Just because you can turn something into a "yes/no" question does not mean you should. -- If someone asked you your status, you wouldn't say "yes".) – BrainSlugs83 Jun 07 '16 at 20:58
  • 1
    @Yuck, Yes I would recommend numbering enums explicitly. The reason is that any re-ordering of the enum items would change their value. Now, if you store those values in a database, the data will be out of sync with the code. If they are explicit, the re-ordering does not change their values. – swbandit Jun 14 '17 at 15:18
  • @BoltClock, not true. The values are important they were created so you can have collections of meaningful values without having to wrangle some consts together and create the association yourself. Not starting at 0 is a good way to establish that they must be set explicitly and prevent errors where an unitialized value appears to be intentionally set. – jterm Jan 24 '19 at 06:13

17 Answers17

187

Framework Design Guidelines:

✔️ DO provide a value of zero on simple enums.

Consider calling the value something like "None." If such a value is not appropriate for this particular enum, the most common default value for the enum should be assigned the underlying value of zero.

Framework Design Guidelines / Designing Flag Enums:

❌ AVOID using flag enum values of zero unless the value represents "all flags are cleared" and is named appropriately, as prescribed by the next guideline.

✔️ DO name the zero value of flag enums None. For a flag enum, the value must always mean "all flags are cleared."

axmrnv
  • 964
  • 10
  • 23
Andrey Taptunov
  • 9,367
  • 5
  • 31
  • 44
  • 33
    Fail early: If 'None' is not appropriate but there's no logical default value, I'd still put a value of zero (and call it 'None' or 'Invalid') that isn't meant to be used, just so that if a class member of that enumeration is not initialized properly, the uninitialized value can easily be spotted, and in `switch` statements it will jump to the `default` section, where I throw a `InvalidEnumArgumentException`. Otherwise a program may unintentionally continue running with the zero value of an enumeration, which may be valid and go unnoticed. – Allon Guralnek Sep 06 '11 at 18:47
  • 2
    @Allon It seems better to only give the enum values you know are valid and then to check for invalid values in the setter and/or constructor. That way you know immediately if some code isn't working correctly rather than allowing an object with invalid data to exist for some unknown time and finding out about it later. Unless 'None' represents a valid state, you shouldn't use it. – wprl Sep 07 '11 at 14:18
  • @SoloBold: That sounds like a case where you don't forget to initialize an enum class member in a constructor. No amount of validation will help you if you forget to initialize or forget to validate. Also, there are simple DTO classes that don't have any constructors, but rely on [object initializers](http://msdn.microsoft.com/en-us/library/bb384062.aspx) instead. Hunting down such a bug can be extremely painful. Nevertheless, adding an unused enumeration value makes for an ugly API. I would avoid it for APIs geared for public consumption. – Allon Guralnek Sep 07 '11 at 18:32
  • @Allon That's a good point, but I would argue that you should be validating enums in the setter function and throwing from the getter if the setter was never called. That way you have one point of failure and you can plan on the field always having a valid value, which simplifies the design and code. My two cents anyway. – wprl Sep 07 '11 at 18:56
  • @SoloBold: Imagine a DTO class with 15 auto-implemented properties. Its body is 15 lines long. Now imagine that same class with regular properties. That's a minimum of 180 lines before adding any verification logic. This class is used exclusively internally for data-transfer purposes only. Which one would you rather maintain, a 15 line class or a 180+ lines class? Succinctness has its value. But still, both our styles are correct, they're simply different. (I guess this is where AOP sweeps in and wins both sides of the argument). – Allon Guralnek Sep 07 '11 at 19:11
  • @AndreyTaptunov But when not using 0 values for enums I encounter "An exception of type 'System.NullReferenceException' occurred in MyProjectName.dll but was not handled in user code. Additional information: Object reference not set to an instance of an object." error. How to fix it? Note: I use an extension method in order to display enum's Descriptions, but not sure how to avoid the error in the helper method (GetDescription) defined on [this page](http://stackoverflow.com/questions/34293468/cannot-display-enum-values-on-kendo-grid/34294589?noredirect=1#comment56355782_34294589) – Jack Dec 16 '15 at 16:55
  • If you allow that no flags have been set on your flags enum, you should totally add a `None = 0` at the top; You can do early aborts in your code that way (i.e. `if (value == MyFlags.None) { return; }`, etc.) – BrainSlugs83 Jun 07 '16 at 20:31
  • A common source of un-initialised enums is when deserialising objects from JSON. JSON deserialisers will not always call your class constructor (potentially circumventing your validation logic), and leave enum values with the default value of `0` if the JSON payload doesn't contain an explicit value. Having `0 = Invalid` would help identify these cases, and/or having validation in the Property setter – Chris Brook Oct 17 '19 at 12:03
78

Well, I guess I stand in disagreement with most answers that say not to explicitly number them. I always explicitly number them, but that is because in most cases I end up persisting them in a data stream where they are stored as an integer value. If you don't explicitly add the values and then add a new value you can break the serialization and then not be able to accurately load old persisted objects. If you are going to do any type of persistent store of these values then I would highly recommend explicitly setting the values.

pstrjds
  • 16,840
  • 6
  • 52
  • 61
  • 10
    +1, agreed, but only in the case where your code relies on the integer for some external reason (eg. serialisation). Everywhere else you should stick with just letting the framework do it's job. If you rely on the integer values internally, then you are probably doing something wrong (see: let the framework do it's job). – Matthew Scharley Aug 31 '11 at 13:43
  • @Matthew - I agree totally. In a lot of the code I write, I am serializing to disk or a database so I tend to persist the integer value, and thus want the values to be defined explicitly. If the enum is never going to be persisted, then I just let the system define the values. As you said, if you are using the integer values for comparison in code then you are doing something wrong. – pstrjds Aug 31 '11 at 14:11
  • 3
    I like to persist the text of the enum. Makes the database far more usable imo. – Dave Aug 31 '11 at 14:46
  • 2
    Normally, you don't need explicitly set the values... even when they get serialized. just do ALWAYS add new values at the end. this will solve serialization problems. otherwise you might need a versioning of your data store (e.g. a file header containing a version for changing the behaviour when reading/writing values) (or see memento pattern) – Beachwalker Aug 31 '11 at 15:59
  • Stegi: Sometimes there may be a logical sequence for an enumeration (e.g. the states of a state machine), and it would be logical to add new states somewhere other than the end. For example, a machine with states "Init WaitForData ReadingData GotData" might need to have a "DiscardInitialJunk" state added between "WaitForData" and "ReadingData". If state values will not be persisted between code rebuilds, the new state should be inserted in its sequential spot (changing later values). If the values will be persisted between builds, however, those later values must not change. – supercat Aug 31 '11 at 16:34
  • 4
    @Dave: Unless you expliclty document that enum texts are sacred, you set yourself up for failure if a future programmer decides to adjust a name to be clearer or conform to some naming convention. – supercat Aug 31 '11 at 16:35
  • @Dave - supercat makes a good point if the name of the enum changes, also it is less space in the DB/persisted file if you are persisting an int vs string. The other area, not mentioned would be streaming over some socket connection/WCF service, etc. In that case I definitely want to stream the int value and not the string to cut down on traffic. – pstrjds Aug 31 '11 at 20:04
  • Where I work we generally serialize to text files, so enum names are preserved. If the enums change value there's no problem most of the time. In a handful of cases, we need to serialize out to binary files. In those cases a CRC is generated, and if the enum is changed the CRC changes, and it asserts on start up and complains loudly until either the cached CRC is updated (which means you need to reexport all the binary files) or the enum is reverted. – Alex Sep 01 '11 at 00:54
  • @supercat: Not if you've got any kind of decent automated testing I'm sure - it's a risk, but it'd be caught If the correct safe guards are in place. I think the benefit of having "theTextOfEnum14" in the database rather than 14 though - makes things much easier to report against, interrogate and generally 'use'. – Dave Sep 01 '11 at 07:36
  • 1
    @pstrjds: it's a stylistic trade off I guess - disk space is cheap though, time spent constantly converting between enum values and an intwhen searching the db is relatively expensive (doubly so if you have a reporting tool setup against a database or something similar). If you're worried abotu space, with the newer SQL server versions you can have a database compressed, which means 1000 occurences of "SomeEnumTextualValue" will use barely any more space. Of course this won't work for all projects - it's a trade off. I think the worry about bandwidth smells like premature optimisation, maybe! – Dave Sep 01 '11 at 07:55
  • @Dave - not sure how converting from an enum to an int is expensive, you can merely cast it, there's no real conversion that has to happen. If I am running a query that is restricted to some enum value, I cast the enum to an int and add it to the string. If I am parsing rows that came out, again, I can simply cast the int to the enum. If I am persisting the enum value as a string, then I have to pay a penalty to convert the enum to string and string to enum. – pstrjds Sep 01 '11 at 12:24
  • @pstrjds: I mean out of code - when looking up the DB '1' means nothing, but 'Active' is readable (even more true when working with lots large enums!). This is also true if you have an ETL or reporting tool in use - the text is usable (Active) rather than '1'. The only way around this is to have multiple points of change... Also, if I swap enum orders or change values (without explicitly setting an int value to each enum) I can easily introduce silly errors when persisting an int. – Dave Sep 01 '11 at 12:37
  • 1
    @Dave - okay, now I follow where you were going (should have been clearer to me since you explicitly mentioned reporting). In the cases where I have been persisting enums, I have in general not had to worry about a user readable form of it, and so have never really thought about that issue. I persist stuff for our software to use. The value is persisted and retrieved from our code. I think I would still end up persisting it as an int and translating it, but I would agree that is a style thing, and I think I could be convinced to do it another way depending on overall design. – pstrjds Sep 01 '11 at 13:10
  • Yea - I agree it's a stylistic choice, and maybe architectural, depending on the application - it's nice to see both methods, particularly the strengths and weaknesses. – Dave Sep 01 '11 at 13:19
  • @supercat surely enum names are sacred already, going by the spirit of the enum? If I ever want/need to change enum names I expect to be extremely careful and very diligent in researching and testing all the side-effects - a name is for life, not just for xmas ;). – Adam Apr 25 '13 at 16:55
  • 1
    @Adam: What's important is that the correlation between name and value last as long as anything that relies upon it. Things which are public should be immortal, as are things which are persisted to external storage. If an enum is neither, then the any code which could rely upon its value sequence will be in a single assembly. Changing an enum value would then be safe if and only if all code in that assembly can handle the change. – supercat Apr 25 '13 at 17:15
  • You should serialize them as strings. But I hear you, sometimes you don't get that choice (i.e. inherited code base, backwards compatibility, etc.) -- but either way, make sure when you use a `[Flags]` enum that when you assign the values you do so by powers of two, instead of just incrementing them. – BrainSlugs83 Jun 07 '16 at 20:33
  • @pstrjds If you persist enum in the database, would you start it from 1? (instead of default 0) – voytek Sep 06 '21 at 16:03
17

An Enum is a value type and its default value (for example for an Enum field in a class) will be 0 if not initialized explicitly.

Therefore you generally want to have 0 as an defined constant (e.g. Unknown).

In your example, if you want Inactive to be the default, then it should have the value zero. Otherwise you might want to consider adding a constant Unknown.

Some people have recommended that you don't explicitly specify values for your constants. Probably good advice in most cases, but there are some cases when you will want to do so:

  • Flags enums

  • Enums whose values are used in interop with external systems (e.g. COM).

Joe
  • 122,218
  • 32
  • 205
  • 338
  • I've found that flags enums are a lot more readable when you *don't* explicitly set the values. -- Also way less error prone to let the compiler do the binary math for you. (i.e. `[Flags] enum MyFlags { None = 0, A, B, Both = A | B, /* etc. */ }` is way more readable, than `[Flags] enum MyFlags { None = 0, A = 1, B = 2, Both = 3, /* etc */ }`.) – BrainSlugs83 Jun 07 '16 at 20:39
  • 1
    @BrainSlugs83 - I don't see how that would be helpful in the general case - e.g. `[Flags] enum MyFlags { None=0, A, B, C }` would result in `[Flags] enum MyFlags { None=0, A=1, B=2, C=3 }`, whereas for a Flags enum you would typically want C=4. – Joe Jun 07 '16 at 21:21
15

Unless you have a specific reason to change it, leave enums with their default values, which begin at zero.

public enum Status : byte
{
    Inactive,
    Active
}
Randal Schwartz
  • 39,428
  • 4
  • 43
  • 70
FishBasketGordo
  • 22,904
  • 4
  • 58
  • 91
8

Unless you have a good reason to use the raw values, you should only ever be using implicit values and referencing them with Status.Active and Status.Inactive.

The catch is that you might want to store data in a flat file or DB, or use a flat file or DB that someone else created. If you're making it yourself, make it so the numbering fits what the Enum is used for.

If the data is not yours, of course you're going to want to use whatever the original dev had used as a numbering scheme.

If you're planning on using the Enum as a set of flags, there is a simple convention that's worth following:

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
}

Values should be powers of two and can be expressed using bit-shift operations. None, obviously should be 0, but All is less obviously -1. ~0 is the binary negation of 0 and results in a number that has every bit set to 1, which represents a value of -1. For compound flags (often used for convenience) other values may be merged using the bitwise or operator |.

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
7

If not specified numbering starts at 0.

It is important to be explicit since enums are often serialized and stored as an int, not a string.

For any enum stored in the database, we always explicitly number the options to prevent shifting and reassignment during maintenance.

According to Microsoft, the recommended convention is use the first zero option to represent an uninitialized or the most common default value.

Below is a shortcut to start numbering at 1 instead of 0.

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

If you wish to set flag values in order to use bit operators on enum values, don't start numbering at the zero value.

dru
  • 81
  • 1
  • 5
7

I would say, it depends on how you use them. For flagging enum it is a good practice to have 0 for None value, like that:

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

When your enum is likely to be mapped to a database lookup table, I'd start it with 1. It should not matter much for professionally written code, but this improves readability.

In other cases I'd leave it as it is, giving no care whether they start with 0 or 1.

Michael Sagalovich
  • 2,539
  • 19
  • 26
5

I'd say best practice is to not number them and let it be implicit - which would start from 0. Since its implicit its the language preference which is always good to follow :)

John Humphreys
  • 37,047
  • 37
  • 155
  • 255
5

I would start a boolean type enum with a 0.

Unless "Inative" means something other than "Inactive" :)

This retains the standard for those.

Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
2

Don't assign any numbers. Just use it like it supposed to be used.

Hooch
  • 28,817
  • 29
  • 102
  • 161
2

If you start at 1, then you can easily get a count of your things.

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

If you start at 0, then use the first one as a value for uninitialized things.

{
    BOX_NO_THING   = 0,
    BOX_THING1     = 1,
    BOX_THING2     = 2,
    BOX_NUM_THING  = BOX_THING2
};
  • 5
    Sorry, Jonathan. I think, this suggestion is a little "old school" in my mind (kind of convention comes from low-level-c years). This is ok as a quick solution to "embed" some addional infos about the enum but this is not a good practice in larger systems. You should not use an enum if you need info about the number of available values etc. And what about a BOX_NO_THING1? Would you give him BOX_NO_THING+1? Enums should be used as what they are meant to be used for: Specific (int) values represented by "speaking" names. – Beachwalker Aug 31 '11 at 17:17
  • Hm. You're assuming it's old school because I used all caps, I guess, rather than MicrosoftBumpyCaseWithLongNames. Although I agree it's better to use iterators than loop until reaching an enum'ed XyzNumDefsInMyEnum definition. – Jonathan Cline IEEE Aug 31 '11 at 21:19
  • This is **terrible** practice in C# in a variety of ways. Now when you go to get a count of your enums the correct way, or if you attempt to enumerate them the correct way, you will get an extra, duplicate object. Also it makes the .ToString() call potentially ambiguous (screwing up modern serialization), among other things. – BrainSlugs83 Jun 07 '16 at 20:46
1

Prefer setting the first Enum member's value to 1 if the Enum does not have a concept of default value for the following reasons.

Intuition

C# sets the Enum to 0 by default. So, unless that first Enum member is really a default value, it's intuitive to not have it mapped to 0.

Allows Enforcing of Required Enums for Web API

Consider the following Minimal Web API:

using Microsoft.AspNetCore.Mvc;
using MiniValidation; // See https://github.com/dotnet/aspnetcore/issues/39063
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Returns true if validation is successful, false otherwise
app.MapGet("/", ([FromBody] MyClass myClass) => MiniValidator.TryValidate(myClass, out _));

app.Run();

class MyClass 
{ 
    [EnumDataType(typeof(MyEnum))] // Validates `MyEnum` is a valid enum value
    public MyEnum MyEnum { get; set; } 
}

enum MyEnum { One, Two }

Suppose that it's mandatory for the client to supply a value for MyEnum; sending an empty JSON string {} results in the endpoint returning false.

However, the above implementation returns true; Model Validation passes because C# defaults MyEnum to 0, which is mapped to MyEnum.One.

By modifying the Enum to enum MyEnum { One = 1, Two }, the endpoint returns false; Model Validation fails because none of the Enum's members are mapped to 0.


Caveat

Enum's guidelines documentation state

DO provide a value of zero on simple enums.

But it doesn't seem that violating this guideline leads to negative consequences.

Zhiyuan-Amos
  • 154
  • 2
  • 6
1

Assign all values starting at 1 and use `Nullable{T}` to represent no value, unless you can't

I appreciate Microsoft's framework guidelines, but I disagree with them on enum practices. I think there is a lot of subtlety in the various use cases of enums that aren't really addressed there, or in other answers here.

However, flag enums really do need a None = 0 value to work. The rest of this answer does not apply to flag enums.

Also, before going on, it might be good to state the golden rule of C# enums:

Enums are a semi-type-safe minefield

For this answer, I will use this hypothetical enum:

enum UserType {
  Basic,
  Admin
}

There are different ways we might use this enum type.


Case 1: Part of a data structure queried from the DB

class UserQueryResult {
  // Name of the saved user
  public string Name { get; set; }

  // Type of the saved user
  public UserType Type { get; set; }

  // Lucky number of the user, if they have one
  public int? LuckyNumber { get; set; }
}

Case 2: Part of a search query

class UserSearchQuery {
  // If set, only return users with this name
  public string Name { get; set; }

  // If set, only return users with this type
  public UserType Type { get; set; }

  // If set, only return users with this lucky number
  public int? LuckyNumber { get; set; }
}

Case 3: Part of a POST request

class CreateUserRequest {
  // Name of user to save
  public string Name { get; set; }

  // Type of user to save
  public UserType Type { get; set; }

  // Lucky number of user, if they have one
  public int? LuckyNumber { get; set; }
}

These three classes all look the same, but the data comes from different places and is validated and processed differently.

Case 1: Part of a data structure queried from the DB

We can make some assumptions about the validity of this data, because it should have been validated before saving.

  • Name should be a valid non-empty string.
  • Type should be either Basic or Admin, never null or some other invalid value. (For now, ignore how this property is persisted, whether as INT/VARCHAR/etc.)
  • Nulls are never valid for Name or Type. If using newer C# language features, the Name property might be declared as non-nullable (string! Name), although this might not be directly supported by all ORMs, and so you may need to validate against nulls after querying data.

Case 2: Part of a search query

This is a client request, so there may be invalid input. Additionally, these properties should be optional, so clients can search using only the filters they care about.

You might want to model this type using Nullable<T> for value types and explicit nullable reference types.

public class UserSearchQuery {
  // Only return users with this name
  public string? Name { get; set; }

  // Only return users with this type
  public UserType? Type { get; set; }

  // If set, only return users with this lucky number
  public int? LuckyNumber { get; set; }
}

Things you may want to validate:

  • Name is either null or a non-empty string. Alternately, you may just treat empty or whitespace as null. (You probably don't want to validate the value is a real user name. If its not valid, the search will return 0 results.)
  • Type is a valid enum value, or some representation of "no filter". For example, if a client sends Type = "Superuser" this may indicate a client bug and a 400 response would be helpful.

Case 3: Part of a POST request

This is also client input, but these properties should not allow null/blank values, and there will be different validation rules.

Things you may want to validate:

  • Name is a non-null, non-empty string
  • Name is at least X characters long
  • Name does not contain punctuation or whitespace
  • Type is a valid value

Like case 1, you may want to use string! Name to more accurately represent your data. However, if this is being parsed from HTTP requests, you may need to explicitly validate against nulls still, depending on the framework you are using.


So, what is the best way to represent "no type"?

The framework guidelines say that we should add an element to our enum to represent this:

enum UserType {
  None,
  Basic,
  Admin
}

So how does this affect our 3 use cases? It affects all of them, because they are all using UserType.

Case 1: Part of a data structure queried from the DB

Instances of UserQueryResult can now be created with Type = UserType.None.

Of course, this isn't the first invalid state our typing allows. UserQueryResult already allowed Name = "", but we are adding a possible invalid state.

In places where we access UserQueryResult.Type, we should already have a defensive way to handle invalid UserType values, since the type system allows things like (UserType)999.

Case 2: Part of a search query

If we stick with using Nullable<T> for value types on our optional properties, we now have two ways to represent "do not filter on UserType".

  • Type = UserType.None
  • Type = null

This means anywhere we use this type we need some && or || logic to deal with both cases.

If we get rid of Nullable<T> on enum types but leave it on other value types, then we reduce the number of options, but have a more complicated API contract with multiple conventions for us and clients to remember.

Case 3: Part of a POST request

The types now allow Type = UserType.None on this request. We'll need to add a special validation rule to check against this.


What we can see from the effects of this change on these 3 cases is that we have coupled the list of valid values to the representation of "no value". "No value" is only valid for Case 2, but we have forced the code for Case 1 and Case 3 to handle extra "no value" complexity.

Additionally, we can see in Case 2 that we already have a generic way to represent "no value" for value types, which is Nullable<T>. In many ways this resembles the null handling for reference types, bringing us close to a single unified way to represent "no value" across all types, reducing developer mental load.

Conclusion 1

Use Nullable<T> for "no value", for consistency, and so that you have a distinct type to represent "a value that is never 'no value'".


So that is why you shouldn't add a None value. But why should you explicitly assign enum int values?

Reason 1: Unassigned properties have the value default(T)

For reference types, default(T) == null. For value types, default(T) == (T)0.

Let's say a client wants to POST a request to create a new user. A good JSON payload would look like this:

{
  "Name": "James",
  "Type": "Admin",
  "LuckyNumber": 12
}

(For readability, our JSON parser is configured to accept strings for enums. Whether using strings or ints for enums in JSON is not really relevant here.)

As expected, this payload will be parsed to a C# object like this:

{
  Name = "James",
  Type = UserType.Admin,
  LuckyNumber = 12
}

What happens if our client sends incomplete JSON?

{
  "Name": "James",
  // Client forgot to add Type property
  "LuckyNumber": 12
}

This will be parsed as

{
  Name = "James",
  Type = default(UserType),
  LuckyNumber = 12
}

Again, default(UserType) == (UserType)0.

Our enum could be declared in one of three ways:

  1. Start with None (None = 0, or just None implicitly assigned to 0)
  2. Start with Admin implicitly assigned to 0
  3. Start with Admin = 1

In case 1, Type gets parsed as None. Since None is part of our enum, we already need to validate against this case to prevent saving None to the DB. However, I already covered the reasons why you shouldn't have a None value.

In case 2, Type gets parsed as Admin. After that happens, there isn't a way to differentiate between an Admin value that came from "Type": "Admin" in the payload, vs Type missing in the payload. This is obviously not good.

In case 3, Type gets parsed as (UserType)0, which doesn't have a name. This looks odd at first, but is actually the best possible scenario. Because enums allow invalid values (like (UserType)999), we should be validating against invalid values from clients anyway. This just makes "unassigned" an invalid value instead of a valid one.

To me, case 3 also seems well aligned with the recent additions to C# that make it harder to represent invalid values: non-nullable reference types and required properties. Conversely, case 1 feels like a legacy pattern from C# 1, before generics and Nullable<T>.

Reason 2: Avoid accidental contract changes

If your enum's integer values are part of the external-facing contract of your service, changing the integers can break clients.

There are two main places enums are external-facing:

  • Saving/loading from a database
  • HTTP requests/responses

Here is the easiest way to accidentally create a breaking change with enums. Start with this enum:

enum UserType {
  Admin,     // = 0
  Superuser, // = 1
  Basic      // = 2
}

Clients are using hardcoded values 0, 1, and 2 for user types. Then the business wants to deprecate the Superuser type. A dev removes that enum element.

enum UserType {
  Admin,     // = 0
  Basic      // = 1
}

How many behaviors are now broken?

  • A client might POST 2 for a Basic user, but it will get a validation error for an invalid Type value
  • A client might POST 1 for a Superuser, but it will save a Basic user
  • A client might GET 1 for a Basic user and think it has a Superuser
  • A client will never GET 2 and will never show Basic user functionality

What if we had assigned explicit values to the enum fields at the beginning, and then removed Superuser?

enum UserType {
  Admin = 1
  // No more Superuser = 2
  Basic = 3
}

No accidental breakage:

  • POSTing and GETing 3 for Basic users still works
  • POSTing 2 for a Superuser will get a validation error for an invalid value
  • A client will never GET 2 and will never show Superuser functionality

The HTTP case can also be mitigated by serializing enums as strings instead of numbers. However, that isn't ideal if you really need to minimize payload sizes. String enum serialization is less common on the DB side, I think because the same team often owns the DB and the service using it, whereas API clients can be more distributed and communication can be more challenging.

Conclusion 2:

Always explicitly assign values to each enum field, to prevent accidental breaking changes. But never assign `0` (except for flag enums), so that you can differentiate between unassigned properties and valid values.
JamesFaix
  • 8,050
  • 9
  • 37
  • 73
0

if the enum starts with zero then no need to assign integer values.It starts with 0 & increment by 1. Inactive = 0, Active = 1.

 public enum Status : byte{
   Inactive,
   Active
}

If you want to assign specific value for 1st one, you need to assign the value for it.Here, Inactive = 1, Active = 0.

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

First of all, unless you're specifying specific values for a reason (the numeric value has meaning somewhere else, i.e. The Database or external service) then don't specify numeric values at all and let them be explicit.

Second of all, you should always have a zero value item (in non-flags enums). That element will be used as the default value.

Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
0

Don't start them at 0 unless there's a reason to, such as using them as indices to an array or list, or if there's some other practical reason (like using them in bitwise operations).

Your enum should start exactly where it needs to. It needn't be sequential, either. The values, if they are explicitly set, need to reflect some semantic meaning or practical consideration. For example, an enum of "bottles on the wall" should be numbered from 1 to 99, while an enum for powers of 4 should probably start at 4 and continue with 16, 64, 256, etc.

Furthermore, adding a zero-valued element to the enum should only be done if it represents a valid state. Sometimes "none," "unknown," "missing," etc. are valid values, but many times they are not.

wprl
  • 24,489
  • 11
  • 55
  • 70
-1

I like to start my enums at 0, since that's the default, but I also like to include a Unknown value, with a value of -1. This then becomes the default and can help with debugging sometimes.

Tomas McGuinness
  • 7,651
  • 3
  • 28
  • 40
  • 4
    Horrible idea. As a value type, enums are always initialized to zero. If you're going to have some value that represents unknown or uninitialized, it needs to be 0. You can't change the default to -1, zero-filling is hard-coded throughout the CLR. – Ben Voigt Aug 31 '11 at 13:19
  • Ah, I didn't realise that. I generally set the value of an enum value/property when I decalre/initialise. Thanks for the pointer. – Tomas McGuinness Aug 31 '11 at 13:26