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


136

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

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

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

public enum Status : byte
{
    Inactive,
    Active
}

13
您真的需要对它们进行明确编号吗?

164
创建枚举只是为了避免像这样的事情变得不重要。
BoltClock

9
@Daniel-哎呀!当您认为布尔值会起作用时,最好使用枚举,而不是在考虑枚举时使用布尔值。
AAT

22
@Daniel当然是因为FileNotFound的值
Joubarc

5
xkcd.com/163应用于枚举甚至比对数组索引更好。
2011年

Answers:


161

框架设计准则

✔️对于简单的枚举,请提供零值。

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

框架设计准则/设计标志枚举

❌避免使用标志枚举值零,除非该值表示“所有标志均已清除”并已正确命名(如下一准则所规定)。

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


28
提前失败:如果“ None”不合适但没有逻辑默认值,则我仍将不打算使用的值设为零(并将其称为“ None”或“ Invalid”),以便如果该枚举的类成员未正确初始化,则可以很容易地发现未初始化的值,并且在switch语句中它将跳到该default部分,在该部分我抛出InvalidEnumArgumentException。否则,程序可能会无意中以枚举的零值继续运行,该值可能是有效的且未被注意。
Allon Guralnek 2011年

1
@Allon最好只提供您知道有效的枚举值,然后在setter和/或构造函数中检查无效值。这样,您可以立即知道某些代码是否无法正常工作,而不是让带有无效数据的对象存在未知的时间,然后稍后再查找。除非“无”代表有效状态,否则您不应使用它。
wprl 2011年

@SoloBold:听起来好像您不会忘记在构造函数中初始化枚举类成员的情况。如果您忘记初始化或忘记验证,没有任何验证方法对您有帮助。另外,有些简单的DTO类没有任何构造函数,而是依赖于对象初始化程序。寻找此类错误可能会非常痛苦。但是,添加未使用的枚举值会使API变得难看。对于面向公众使用的API,我会避免使用它。
Allon Guralnek 2011年

@Allon很好,但是我认为您应该验证setter函数中的枚举,如果从未调用过setter,则应从getter中抛出。这样,您就有了一个故障点,并且可以在始终具有有效值的领域进行计划,从而简化了设计和代码。反正我的两分钱。
wprl 2011年

@SoloBold:想象一个具有15个自动实现属性的DTO类。它的身体长15行。现在想象一下具有常规属性的同一个类。在添加任何验证逻辑之前,至少需要180行。此类仅在内部用于数据传输。您希望维护15个线类还是180个以上的线类中的哪一个?简洁有其价值。但是,我们两种风格都是正确的,只是完全不同。(我猜这是AOP席卷而论证双方的地方)。
Allon Guralnek 2011年

66

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


9
+1,同意,但仅在您的代码出于某些外部原因(例如序列化)而依赖整数的情况下。您应该坚持让框架在其他地方工作。如果您内部依赖整数值,那么您可能做错了什么(请参阅:让框架来完成此工作)。
马修·沙利

3
我喜欢坚持列举的文字。使数据库更加可用imo。
戴夫

2
通常,即使序列化了值,也不需要显式设置它们。总是在末尾添加新值。这将解决序列化问题。否则,您可能需要对数据存储进行版本控制(例如,文件头包含用于在读取/写入值时更改行为的版本)(或查看内存模式)
Beachwalker

4
@Dave:除非您明确表明枚举文本是神圣的,否则如果将来的程序员决定调整名称使其更清楚或更符合某些命名约定,则您将自己设置为失败。
supercat

1
@pstrjds:这是一种风格上的折衷,我猜想-磁盘空间很便宜,在枚举值之间不断转换以及在搜索数据库时不断花费的时间相对来说比较昂贵(因此,如果您有针对数据库或类似工具的报告工具设置,那会比较昂贵) 。如果您担心空间不足,可以使用较新的SQL Server版本压缩数据库,这意味着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 },而对于Flags枚举,通常需要C = 4。

14

除非有特殊原因需要更改,否则将枚举保留为默认值,默认值从零开始。

public enum Status : byte
{
    Inactive,
    Active
}

6

我要说的最佳实践是不对它们进行编号,并使其为隐式-将从0开始。由于其隐式是其语言偏好,因此总是可以遵循的:)


6

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

除非“ Inative”表示“ Inactive”以外的其他内容:)

这保留了这些标准。


6

我会说,这取决于您如何使用它们。对于标记枚举,将None值的0设置为一个好习惯,如下所示:

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

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

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


5

除非你有一个很好的理由使用原始值,你应该永远只能是使用隐式值与引用它们Status.ActiveStatus.Inactive

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

值应为2的幂,并且可以使用位移运算来表示。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
抱歉,乔纳森。我认为,这个建议在我心中有点“老派”(有点惯例来自低级c年)。可以将其作为“快速”嵌入有关枚举的一些其他信息的快速解决方案,但这在大型系统中不是一个好习惯。如果您需要有关可用值数量等信息,则不应使用枚举。BOX_NO_THING1又如何呢?您能给他BOX_NO_THING + 1吗?枚举应该用作它们的含义:用“口头”名称表示的特定(int)值。
海滩漫步

嗯 您假设这是一所古老的学校,因为我猜我使用了所有大写字母,而不是使用MicrosoftBumpyCaseWithLongNames。尽管我同意,最好使用迭代器而不是循环,直到达到枚举的XyzNumDefsInMyEnum定义为止。
Jonathan Cline IEEE

这是C#中各种方式的可怕实践。现在,当您以正确的方式获取枚举计数时,或者尝试以正确的方式枚举枚举时,您将获得一个额外的重复对象。除此之外,它还会使.ToString()调用可能含糊不清(增加了现代序列化)。
BrainSlugs83

0

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

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


0

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

enum应该从需要的地方开始。也不必是顺序的。如果值是明确设置的,则需要反映一些语义或实际考虑。例如,enum“墙上的瓶子”中的a应该从1到99进行编号,而enum4的幂可能应该从4开始并以16、64、256等继续。

此外,enum仅当元素表示有效状态时,才应向其中添加零值元素。有时“无”,“未知”,“缺失”等是有效值,但很多时候不是。


-1

我喜欢将枚举值从0开始,因为这是默认值,但是我也喜欢包含一个值为-1的Unknown值。这将成为默认值,有时可以帮助调试。


4
可怕的主意。作为值类型,枚举始终被初始化为零。如果要具有代表未知或未初始化的值,则该值必须为0。您无法将默认值更改为-1,因此在整个CLR中,零填充都是硬编码的。
Ben Voigt

啊,我没意识到。我通常在进行除垢/初始化时设置枚举值/属性的值。感谢您的指导。
Tomas McGuinness,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.