枚举应该以 0 还是 1 开头?

发布于 2024-12-02 01:52:15 字数 278 浏览 3 评论 0原文

想象一下我定义了以下枚举:

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

使用枚举的最佳实践是什么?它应该像上面的示例一样以 1 开头,还是以 0 开头(没有显式值),如下所示:

public enum Status : byte
{
    Inactive,
    Active
}

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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

夜清冷一曲。 2024-12-09 01:52:16

除非您有特定原因要更改它,否则请保留枚举的默认值(从零开始)。

public enum Status : byte
{
    Inactive,
    Active
}

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
}
星軌x 2024-12-09 01:52:16

除非您有充分的理由使用原始值,否则您应该只使用隐式值并通过 Status.ActiveStatus.Inactive 引用它们。

问题是您可能希望将数据存储在平面文件或数据库中,或者使用其他人创建的平面文件或数据库。如果您自己制作,请使其编号适合 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
}

值应该是 2 的幂,并且可以使用位移运算来表示。 None,显然应该是0,但是All不太明显-1~00 的二进制否定,结果是每个位都设置为 1 的数字,代表 -1 值。对于复合标志(通常为方便起见而使用),可以使用按位或运算符 | 合并其他值。

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 |.

十雾 2024-12-09 01:52:16

我想说,这取决于你如何使用它们。对于标记枚举,最好将 None 值设置为 0,如下所示:

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

当您的枚举可能映射到数据库查找表时,我会从 1 开始。它不应该对于专业编写的代码来说很重要,但这提高了可读性。

在其他情况下,我会保持原样,不关心它们是以 0 还是 1 开头。

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.

扮仙女 2024-12-09 01:52:16

如果未指定,则从 0 开始编号。

明确这一点很重要,因为枚举通常被序列化并存储为 int,而不是字符串。

对于存储在数据库中的任何枚举,我们总是显式地对选项进行编号,以防止在维护期间发生转移和重新分配。

根据微软的说法,推荐的约定是使用第一个零选项来表示未初始化或最常见的默认值。

下面是从 1 而不是 0 开始编号的快捷方式。

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

如果您希望设置标志值以便对枚举值使用位运算符,请不要从零值开始编号。

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.

你的他你的她 2024-12-09 01:52:16

我想说最好的做法是不给它们编号并让它隐式 - 这将从 0 开始。因为它是隐式的,所以它的语言偏好总是很好遵循:)

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 :)

桃扇骨 2024-12-09 01:52:16

我会以 0 开始一个布尔类型枚举。

除非“Inative”意味着“Inactive”以外的东西:)

这保留了这些的标准。

I would start a boolean type enum with a 0.

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

This retains the standard for those.

水水月牙 2024-12-09 01:52:16

分配所有从 1 开始的值,并使用 `Nullable{T}` 来表示没有值,除非你不能

欣赏 Microsoft 的 框架指南,但我不同意他们关于枚举实践的观点。我认为枚举的各种用例中有很多微妙之处,这些用例并没有真正在此处得到解决,也没有在此处的其他答案中得到解决。

但是,标志枚举确实需要一个 None = 0 值才能工作。这个答案的其余部分不适用于标志枚举。

另外,在继续之前,最好先说明C# 枚举的黄金法则:

枚举是半类型安全的雷区

对于这个答案,我将使用这个假设的枚举:

enum UserType {
  Basic,
  Admin
}

有不同的方法我们可能会使用这个枚举类型。


情况 1:从数据库查询的数据结构的一部分

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; }
}

情况 2:搜索查询的一部分

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; }
}

情况 3:POST 请求的一部分

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; }
}

这些三个类看起来都一样,但数据来自不同的地方,并且经过不同的验证和处理。

情况1:从数据库查询的数据结构的一部分

我们可以对此数据的有效性做出一些假设,因为它应该在保存之前经过验证。

  • Name 应该是有效的非空字符串。
  • Type 应该是 BasicAdmin,绝不能是 null 或其他无效值。 (目前,忽略此属性的持久化方式,无论是作为 INT/VARCHAR/etc。)Null
  • 对于 Name永远无效>输入。如果使用较新的 C# 语言功能,Name 属性可能会声明为不可为 null (string!Name),尽管并非所有 ORM 都直接支持这一点,因此您可能需要在查询数据后验证空值。

情况 2:搜索查询的一部分

这是客户端请求,因此可能存在无效输入。此外,这些属性应该是可选的,因此客户端可以仅使用他们关心的过滤器进行搜索。

您可能希望使用 Nullable 对于值类型和显式可为 null 的引用类型来建模此类型。

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; }
}

您可能想要验证的内容:

  • Namenull 或非空字符串。或者,您可以将空或空白视为 null。 (您可能不想验证该值是否是真实的用户名。如果无效,搜索将返回 0 个结果。)
  • Type 是一个有效的枚举值,或者是“no”的某种表示形式筛选”。例如,如果客户端发送 Type = "Superuser",这可能表明客户端存在错误,400 响应会有所帮助。

情况 3:POST 请求的一部分

这也是客户端输入,但这些属性不应允许 null/blank 值,并且会有不同的验证规则。

您可能需要验证的事项:

  • Name 是非 null、非空字符串
  • Name 的长度至少为 X 个字符
  • Name 不包含标点符号或空格
  • Type 是有效值

与情况 1 类似,您可能需要使用 string!名称以更准确地表示您的数据。但是,如果这是从 HTTP 请求解析的,您可能仍然需要显式验证空值,具体取决于您使用的框架。


那么,表示“无类型”的最佳方式是什么?

框架指南指出,我们应该向枚举中添加一个元素来表示这一点:

enum UserType {
  None,
  Basic,
  Admin
}

那么这对我们的 3 个用例有何影响?它会影响所有这些,因为它们都使用 UserType

情况 1:从数据库查询的数据结构的一部分

现在可以使用 Type = UserType.None 创建 UserQueryResult 实例。

当然,这并不是我们的输入允许的第一个无效状态。 UserQueryResult 已允许 Name = "",但我们正在添加可能无效的状态。

在我们访问 UserQueryResult.Type 的地方,我们应该已经有一种防御方法来处理无效的 UserType 值,因为类型系统允许诸如 (UserType)999 之类的事情

情况 2:搜索查询的一部分

如果我们坚持使用 Nullable 作为可选属性上的值类型,我们现在有两种方法来表示“不过滤”关于用户类型”。

  • Type = UserType.None
  • Type = null

这意味着我们在使用此类型的任何地方都需要一些 &&||< /code> 处理这两种情况的逻辑。

如果我们在枚举类型上去掉 Nullable 而将其保留在其他值类型上,那么我们会减少选项的数量,但会有一个更复杂的 API 契约,其中包含多种约定,供我们和客户使用记住。

情况 3:POST 请求的一部分

这些类型现在允许对此请求使用 Type = UserType.None。我们需要添加一个特殊的验证规则来检查这一点。


从此更改对这 3 种情况的影响中我们可以看到,我们将有效值列表与“无值”的表示相结合。 “无值”仅对情况 2 有效,但我们强制情况 1 和情况 3 的代码处理额外的“无值”复杂性。

此外,在案例 2 中我们可以看到,我们已经有了一种表示值类型“无值”的通用方法,即 Nullable。在许多方面,这类似于引用类型的 null 处理,使我们接近于用单一统一的方式来表示所有类型的“无值”,从而减少开发人员的心理负担。

结论 1

使用 Nullable 表示“无值”,以保持一致性,这样您就有一个不同的类型来表示“一个从不‘无值’的值”。


这就是为什么您不应该添加 None 值。但为什么要显式分配枚举 int 值呢?

原因 1:未分配的属性具有值 default(T)

对于引用类型,default(T) == null
对于值类型,default(T) == (T)0

假设客户端想要发布请求来创建新用户。一个好的 JSON 有效负载如下所示:(

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

为了便于阅读,我们的 JSON 解析器被配置为接受枚举字符串。在 JSON 中使用字符串还是整数作为枚举在这里并不真正相关。)

正如预期的那样,此有效负载将被解析为像这样的 C# 对象:

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

如果我们的客户端发送不完整的 JSON,会发生什么?

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

这将再次被解析为

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

default(UserType) == (UserType)0

我们的枚举可以通过以下三种方式之一进行声明:

  1. None 开头(None = 0,或者只是将 None 隐式分配给 0)
  2. Admin 隐式分配给 0 开始
  3. Admin = 1 开始

在情况 1 中,Type被解析为None。由于 None 是我们枚举的一部分,因此我们需要针对这种情况进行验证,以防止将 None 保存到数据库中。不过,我已经介绍了不应使用 None 值的原因。

在情况 2 中,Type 被解析为 Admin。发生这种情况后,无法区分来自负载中的 "Type": "Admin"Admin 值与 Type<有效负载中缺少 /code>。这显然不好。

在情况 3 中,Type 被解析为 (UserType)0,它没有名称。这乍一看很奇怪,但实际上是最好的情况。由于枚举允许无效值(例如 (UserType)999),因此我们无论如何都应该针对来自客户端的无效值进行验证。这只会使“未分配”成为无效值而不是有效值。

对我来说,情况 3 似乎也与 C# 最近添加的内容非常一致,这些添加内容使得表示无效值变得更加困难:不可为 null 的引用类型和必需的属性。相反,情况 1 感觉像是 C# 1 中泛型和 Nullable之前的遗留模式。

原因 2:避免意外的合约更改

如果枚举的整数值是服务面向外部的合约的一部分,则更改整数可能会破坏客户端。

枚举有两个主要面向外部的地方:

  • 从数据库保存/加载
  • HTTP 请求/响应

这是使用枚举意外创建重大更改的最简单方法。从这个枚举开始:

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

客户端对用户类型使用硬编码值 012。然后,企业希望弃用超级用户类型。开发人员删除了该枚举元素。

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

现在有多少行为被打破了?

  • 客户端可能会为基本用户 POST 2,但会收到无效 Type 值的验证错误
  • 客户端可能会为超级用户 POST 1,但会保存一个基本用户
  • 客户端可能会 GET 1 表示基本用户,并认为它有一个超级用户
  • 客户端永远不会 GET 2 并且永远不会显示基本用户功能

如果我们显式分配了会怎样值添加到开头的枚举字段,然后删除超级用户

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

无意外损坏:

  • 基本用户的 POST 和 GET 3 仍然有效 对于
  • 超级用户的 POST 2 将收到无效值的验证错误
  • 客户端永远不会 GET 2 并且永远不会显示超级用户功能

HTTP 情况也可以通过将枚举序列化为字符串而不是数字来缓解。但是,如果您确实需要最小化有效负载大小,那么这并不理想。字符串枚举序列化在数据库端不太常见,我认为是因为同一个团队通常拥有数据库和使用它的服务,而 API 客户端可能更加分散,通信可能更具挑战性。

结论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.

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.

陌上芳菲 2024-12-09 01:52:16

不要分配任何数字。
就像应该使用的那样使用它。

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

江湖彼岸 2024-12-09 01:52:16

如果你从 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
};

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
};
老街孤人 2024-12-09 01:52:16

如果由于以下原因 Enum 没有默认值的概念,则最好将第一个 Enum 成员的值设置为 1

Intuition

C# 默认将 Enum 设置为 0。因此,除非第一个 Enum 成员确实是默认值,否则直观上不将其映射到 0

允许强制执行 Web API 所需的枚举

考虑以下最小 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 }

假设客户端强制为 MyEnum 提供值;发送空 JSON 字符串 {} 会导致端点返回 false

然而,上面的实现返回true;模型验证通过,因为 C# 默认 MyEnum0,它映射到 MyEnum.One

通过将 Enum 修改为 enum MyEnum { One = 1, Two },端点返回 false;模型验证失败,因为没有任何 Enum 成员映射到 0


警告

Enum 的指南文档 状态

请在简单枚举上提供零值。

但违反这一准则似乎并不会导致负面后果。

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.

待天淡蓝洁白时 2024-12-09 01:52:16

首先,除非您出于某种原因指定特定值(数值在其他地方具有含义,即数据库或外部服务),否则根本不要指定数值并让它们明确。

其次,您应该始终有一个零值项(在非标志枚举中)。该元素将用作默认值。

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.

旧人哭 2024-12-09 01:52:16

不要从 0 开始,除非有原因,例如将它们用作数组或列表的索引,或者有其他实际原因(例如在按位运算中使用它们)。

您的enum应该准确地从它需要的地方开始。它也不必是连续的。如果明确设置这些值,则需要反映某些语义或实际考虑。例如,“墙上的瓶子”的枚举应该从1到99编号,而4的幂的枚举可能应该从4开始并以16继续、64、256 等。

此外,只有在表示有效状态时才应将零值元素添加到枚举中。有时“无”、“未知”、“缺失”等是有效值,但很多时候它们不是。

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.

坠似风落 2024-12-09 01:52:16

如果枚举以零开头,则无需分配整数值。它以 0 & 开头增加 1。Inactive = 0,Active = 1。

 public enum Status : byte{
   Inactive,
   Active
}

如果要为第一个指定特定值,则需要为其指定值。这里,Inactive = 1,Active = 0。

 public enum Status : byte{
    Inactive =1,
    Active =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
 }
优雅的叶子 2024-12-09 01:52:16

我喜欢从 0 开始枚举,因为这是默认值,但我也喜欢包含一个值为 -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.

最笨的告白 2024-12-09 01:52:15

框架设计指南

✔️ 请在简单枚举上提供零值。

考虑将该值称为“None”。如果这样的值不适合此特定枚举,则应为该枚举的最常见默认值分配基础值零。

框架设计指南/设计标志枚举:

❌避免使用零标志枚举值,除非该值表示“所有标志都已清除”并且按照下一个指南的规定进行了适当的命名。

✔️ 请务必将标志枚举的零值命名为 None。对于标志枚举,该值必须始终表示“所有标志均已清除。”

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."

童话 2024-12-09 01:52:15

好吧,我想我不同意大多数不明确编号的答案。我总是对它们进行显式编号,但这是因为在大多数情况下,我最终将它们保存在数据流中,并将它们存储为整数值。如果您不显式添加值,然后添加新值,则可能会破坏序列化,然后无法准确加载旧的持久对象。如果您打算对这些值进行任何类型的持久存储,那么我强烈建议显式设置这些值。

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.

所有深爱都是秘密 2024-12-09 01:52:15

Enum 是一种值类型,如果未显式初始化,其默认值(例如类中的 Enum 字段)将为 0。

因此,您通常希望将 0 作为定义的常量(例如未知)。

在您的示例中,如果您希望 Inactive 成为默认值,那么它的值应该为零。否则,您可能需要考虑添加常量Unknown

有些人建议您不要显式指定常量的值。在大多数情况下可能是个好建议,但在某些情况下您会想要这样做:

  • 标记枚举

  • 其值用于与外部系统(例如COM)互操作的枚举。< /p>

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文