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

154

假设我已经定义了以下枚举:

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

如何最好地使用枚举类型?是像上面的例子一样从 1 开始,还是不带显式值从 0 开始,例如:

public enum Status : byte
{
    Inactive,
    Active
}

14
你是否真的需要明确地给它们编号? - Yuck
169
枚举类型的创建正是为了使类似这样的问题不再重要。 - BoltClock
9
@Daniel -- 哎呀!如果你觉得布尔值可以解决问题,最好使用枚举类型,而不是在考虑枚举类型时使用布尔值。 - AAT
22
由于FileNotFound的取值,当然。 - Joubarc
7
http://xkcd.com/163/ 这个漫画比它适用于数组索引更适用于枚举类型。 - leftaroundabout
显示剩余7条评论
17个回答

188

Framework Design Guidelines(框架设计准则)

✔️ 在简单的枚举类型中提供零值。

如果这个值在该枚举类型中不恰当,可以考虑称其为“None”。对于该枚举类型最常见的默认值应该赋予底层零值。

Framework Design Guidelines / Designing Flag Enums(框架设计准则 / 设计 Flag 枚举类型)

❌ 避免使用枚举类型中零值,除非该值表示“所有标志都已清除”并命名得当,如下一条指南所述。

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


33
尽早失败:如果“None”不合适但又没有逻辑上的默认值,我仍然会赋一个零值(并称其为“None”或“无效”),以便在枚举类成员未正确初始化时,可以轻松地发现未初始化的值。在switch语句中,它将跳转到“default”部分,在那里我会抛出一个InvalidEnumArgumentException。否则,程序可能会无意中继续运行具有枚举的零值,这可能是有效的,却不被注意到。 - Allon Guralnek
2
@Allon,最好只给出你知道的有效枚举值,并在setter和/或构造函数中检查无效值。这样,你立即就知道是否有代码不能正常工作,而不是允许存在具有无效数据的对象一段时间,后来才发现它的问题。除非“None”表示一个有效状态,否则不应该使用它。 - wprl
@SoloBold:想象一下,有一个包含15个自动实现属性的DTO类。它的主体有15行长。现在想象一下同样的类具有常规属性。在添加任何验证逻辑之前,至少需要180行。此类仅用于内部数据传输目的。你更愿意维护哪一个,一个15行的类还是一个超过180行的类?简洁性很有价值。但是,我们两种风格都是正确的,只是不同而已。(我猜这就是AOP进入并赢得双方争论的地方)。 - Allon Guralnek
@AndreyTaptunov 但是当不使用枚举的0值时,我遇到了“System.NullReferenceException”异常,在MyProjectName.dll中发生,但未在用户代码中处理。其他信息:对象引用未设置为对象的实例。如何解决?注意:我使用扩展方法来显示枚举的描述,但不确定如何避免在此页面上定义的帮助程序方法(GetDescription)中出现错误。 - Jack
如果您允许在标志枚举上没有设置标志,那么应该在顶部添加None = 0;这样可以通过 if(value == MyFlags.None){ return; }等方式来进行代码早期中止。 - BrainSlugs83
显示剩余3条评论

78

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


10
+1,同意。但仅适用于您的代码由于某些外部原因(例如串行化)而依赖于整数时,在其他情况下,应坚持让框架去完成其工作。如果您在内部依赖于整数值,则可能存在问题(请参见:让框架去完成其工作)。 - Matthew Scharley
3
我喜欢将枚举的文本持久化。在我看来,这能使数据库更易用。 - Dave
2
通常情况下,您不需要显式设置值...即使它们被序列化。只需始终在末尾添加新值即可解决序列化问题。否则,您可能需要对数据存储进行版本控制(例如,包含版本的文件头,在读取/写入值时更改行为)(或查看备忘录模式)。 - Beachwalker
4
@Dave:除非您明确记录枚举文本是神圣的,否则如果未来的程序员决定调整名称以使其更清晰或符合某些命名约定,您将面临失败的风险。 - supercat
1
@Adam:重要的是,名称和值之间的关联应该持续与依赖它的任何内容一样长。公共的东西应该是不朽的,就像被持久化到外部存储器中的东西一样。如果一个枚举既不是公共的也不是被持久化的,那么可能依赖于其值序列的任何代码都将在单个程序集中。只有当该程序集中的所有代码都可以处理更改时,更改枚举值才是安全的。 - supercat
显示剩余13条评论

17

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

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

在您的示例中,如果想将Inactive作为默认值,则应将其值设置为零。否则,您可能需要考虑添加一个常量Unknown

有些人建议不要明确指定常量的值。在大多数情况下这是个好建议,但某些情况下你需要这样做:

  • 标志(Flags)枚举

  • 其值用于与外部系统(例如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 },而对于 Flags 枚举,通常希望 C=4。 - Joe

15

除非你有特定的原因需要更改它,否则请将枚举保留其默认值,这些默认值从零开始。

public enum Status : byte
{
    Inactive,
    Active
}

8
除非你有使用原始值的充分理由,否则应该只使用隐式值,并引用它们与Status.ActiveStatus.Inactive
但问题在于,你可能想要将数据存储在平面文件或数据库中,或者使用别人创建的平面文件或数据库。如果是自己创建,需保证编号适合枚举所用。
如果数据不属于你,则当然要使用原始开发人员使用的编号方案。
如果计划将枚举用作标志集,最好遵循一个简单的约定:
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的值。对于复合标志(通常用于方便),其他值可能会使用按位或运算符|合并。


7

我认为,这取决于您如何使用它们。对于枚举标记来说,将0用作None值是一个好习惯,像这样:

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

当你的枚举很可能被映射到数据库查找表时,我会从1开始。这对于专业编写的代码来说并不重要,但可以提高可读性。
在其他情况下,我会将其保留为原样,不论它们是从0还是1开始。

7
如果没有指定,编号将从0开始。
由于枚举通常被序列化并存储为int而不是字符串,因此明确表达很重要。
对于存储在数据库中的任何枚举,我们总是明确地对选项进行编号,以防止在维护期间发生移动和重新分配。
根据Microsoft的建议约定,使用第一个零选项来表示未初始化或最常见的默认值。
以下是一个快捷方式,可以从1开始编号,而不是从0开始。
public enum Status : byte
{
    Inactive = 1,
    Active
}

如果你希望在枚举值上使用位运算符来设置标志值,不要从零值开始编号。

5

我认为最好的做法是不给它们编号,让它隐式地从0开始。由于它是隐式的,因此遵循语言首选项始终是个好习惯 :)


5

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

除非"Inative"意味着与"Inactive"不同的含义 :)

这将保持标准化。


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
抱歉,Jonathan。在我看来,这个建议有点“老派”了(这种惯例源自低级c语言)。虽然这是一种快速“嵌入”枚举类型的其他信息的方法,但在更大的系统中,这并不是一个好的做法。如果需要有关可用值数量等信息,则不应使用枚举。那么,BOX_NO_THING1怎么办?你会给它BOX_NO_THING+1吗?枚举应该按照既定用途使用:由“口语化”的名称表示的特定(int)值。 - Beachwalker
嗯,你认为我使用全大写字母就是老派的做法了吧,而不是使用MicrosoftBumpyCaseWithLongNames。虽然我同意使用迭代器比循环到枚举类型XyzNumDefsInMyEnum定义更好。 - Jonathan Cline IEEE
这是C#中的可怕做法,有很多方面都是如此。现在,当您以正确的方式获取枚举计数或尝试以正确的方式枚举它们时,您将获得额外的重复对象。此外,它使.ToString()调用可能存在歧义(破坏现代序列化),还有其他一些问题。 - BrainSlugs83

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接