【问题标题】:Should an Enum start with a 0 or a 1?枚举应该以 0 还是 1 开头?
【发布时间】:2011-11-07 14:46:34
【问题描述】:

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

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

使用枚举的最佳做法是什么?它应该像上面的例子一样以1 开头,还是像这样以0 开头(没有显式值):

public enum Status : byte
{
    Inactive,
    Active
}

【问题讨论】:

  • 你真的需要明确地给它们编号吗?
  • 创建枚举只是为了让这样的事情不重要。
  • @Daniel -- 不!当您认为布尔值时使用枚举比在考虑枚举时使用布尔值更好。
  • @Daniel 当然是因为 FileNotFound 值
  • xkcd.com/163 适用于枚举甚至比适用于数组索引更好。

标签: c# .net enums


【解决方案1】:

Framework Design Guidelines:

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

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

Framework Design Guidelines / Designing Flag Enums:

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

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

【讨论】:

  • 提前失败:如果“无”不合适但没有逻辑默认值,我仍然会输入一个不合适的值(并称其为“无”或“无效”)打算使用,只是为了如果该枚举的类成员未正确初始化,则可以很容易地发现未初始化的值,并且在switch 语句中它将跳转到default 部分,我在其中抛出一个@987654326 @。否则,程序可能会无意中以枚举的零值继续运行,这可能是有效的并且不会被注意到。
  • @Allon 最好只给出你知道有效的枚举值,然后检查设置器和/或构造函数中的无效值。这样一来,您可以立即知道某些代码是否无法正常工作,而不是允许具有无效数据的对象存在一段时间并在以后发现它。除非“无”表示有效状态,否则不应使用它。
  • @SoloBold:这听起来像是您不会忘记在构造函数中初始化枚举类成员的情况。如果您忘记初始化或忘记验证,再多的验证也无济于事。此外,还有一些简单的 DTO 类没有任何构造函数,而是依赖于object initializers。寻找这样的错误可能会非常痛苦。然而,添加一个未使用的枚举值会导致 API 变得丑陋。对于面向公共消费的 API,我会避免使用它。
  • @Allon 这是一个很好的观点,但我认为你应该在 setter 函数中验证枚举,如果 setter 从未被调用过,则从 getter 中抛出。这样一来,您就有了一个故障点,并且您可以在始终具有有效值的领域进行计划,从而简化了设计和代码。反正我的两美分。
  • @SoloBold:想象一个具有 15 个自动实现属性的 DTO 类。它的身体长15行。现在想象具有常规属性的同一个类。在添加任何验证逻辑之前,这至少是 180 行。此类仅在内部用于数据传输目的。您更愿意维护哪一个,15 行类还是 180+ 行类?简洁有它的价值。但是,我们的两种风格都是正确的,它们只是不同而已。 (我想这就是 AOP 席卷并赢得双方争论的地方)。
【解决方案2】:

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

【讨论】:

  • +1,同意,但仅在您的代码由于某些外部原因(例如序列化)而依赖整数的情况下。在其他任何地方,您都应该坚持让框架完成它的工作。如果您在内部依赖整数值,那么您可能做错了什么(请参阅:让框架完成它的工作)。
  • 我喜欢持久化枚举的文本。使数据库更加可用 imo。
  • 通常情况下,您不需要显式设置值......即使它们被序列化。只是总是在最后添加新值。这将解决序列化问题。否则您可能需要对数据存储进行版本控制(例如,包含用于在读取/写入值时更改行为的版本的文件头)(或参见备忘录模式)
  • @Dave:除非你明确说明枚举文本是神圣的,否则如果未来的程序员决定将名称调整为更清晰或符合某些命名约定,你就会失败。
  • @pstrjds:我猜这是一种风格上的折衷 - 虽然磁盘空间很便宜,但在搜索数据库时不断在枚举值和 int 之间转换所花费的时间相对昂贵(如果你有一个报告工具,那就更是如此针对数据库或类似的东西进行设置)。如果您担心空间不足,使用较新的 SQL Server 版本,您可以压缩数据库,这意味着 1000 次“SomeEnumTextualValue”将几乎不使用更多空间。当然,这并不适用于所有项目——这是一种权衡。我认为对带宽的担忧听起来像是过早的优化,也许吧!
【解决方案3】:

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

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

在您的示例中,如果您希望 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 - 我看不出这在一般情况下会有什么帮助 - 例如[Flags] enum MyFlags { None=0, A, B, C } 将导致 [Flags] enum MyFlags { None=0, A=1, B=2, C=3 },而对于 Flags 枚举,您通常需要 C=4。
【解决方案4】:

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

public enum Status : byte
{
    Inactive,
    Active
}

【讨论】:

    【解决方案5】:

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

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

    当您的枚举可能映射到数据库查找表时,我会从 1 开始。这对于专业编写的代码应该没有多大关系,但这会提高可读性。

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

    【讨论】:

      【解决方案6】:

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

      问题是您可能希望将数据存储在平面文件或数据库中,或者使用其他人创建的平面文件或数据库。如果您自己制作,请使其编号符合 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~00 的二进制否定,并导致每个位都设置为 1which represents a value of -1 的数字。对于复合标志(通常为方便起见),可以使用按位或运算符| 合并其他值。

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          除非“Inative”是指“Inactive”以外的东西:)

          这保留了这些标准。

          【讨论】:

            【解决方案9】:

            如果没有指定,编号从 0 开始。

            由于枚举通常被序列化并存储为 int,而不是字符串,因此显式很重要。

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

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

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

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

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

            【讨论】:

              【解决方案10】:

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

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                • 对不起,乔纳森。我认为,这个建议在我看来有点“老派”(一种惯例来自低级c年)。这可以作为“嵌入”一些关于枚举的附加信息的快速解决方案,但这在大型系统中不是一个好的做法。如果您需要有关可用值的数量等信息,则不应使用枚举。那么 BOX_NO_THING1 呢?你会给他BOX_NO_THING+1吗?枚举应该被用作它们的用途:由“说话”名称表示的特定 (int) 值。
                • 嗯。您假设它是老派,因为我猜我使用了所有大写字母,而不是 MicrosoftBumpyCaseWithLongNames。尽管我同意在达到枚举的 XyzNumDefsInMyEnum 定义之前,使用迭代器比使用循环更好。
                • 这是在 C# 中以多种方式进行的可怕实践。现在,当您以正确的方式对枚举进行计数时,或者如果您尝试以正确的方式枚举它们,您将获得一个额外的重复对象。此外,它还使 .ToString() 调用可能不明确(搞砸了现代序列化)等等。
                【解决方案12】:

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

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

                【讨论】:

                  【解决方案13】:

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

                  您的enum 应该准确地从它需要的位置开始。它也不必是顺序的。如果明确设置了这些值,则需要反映一些语义或实际考虑。例如,“墙上的瓶子”的 enum 应该从 1 到 99 编号,而 4 的幂的 enum 应该从 4 开始,然后继续 16、64、256 等。

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

                  【讨论】:

                    【解决方案14】:

                    如果枚举以零开头,则无需分配整数值。它以 0 开头并递增 1。非活动 = 0,活动 = 1。

                     public enum Status : byte{
                       Inactive,
                       Active
                    }
                    

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

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

                    【讨论】:

                      【解决方案15】:

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

                      【讨论】:

                      • 可怕的想法。作为一种值类型,枚举始终初始化为零。如果您要获得一些表示未知或未初始化的值,则它需要为 0。您不能将默认值更改为 -1,零填充在整个 CLR 中都是硬编码的。
                      • 啊,我没有意识到这一点。我通常在 decalre/initialise 时设置枚举值/属性的值。谢谢指点。
                      猜你喜欢
                      • 2011-03-10
                      • 2021-07-02
                      • 2016-04-21
                      • 1970-01-01
                      • 1970-01-01
                      • 2010-10-21
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多