如何在C#中处理枚举0(CA1008讨论)

如何在C#中处理枚举0(CA1008讨论),c#,enums,code-analysis,C#,Enums,Code Analysis,规则指定所有枚举都应该有一个名为Unknown的0值(我们这里不讨论标志)。我理解您希望防止未初始化的值自动获得意义的原因。假设我定义以下枚举: enum Gender { Male, Female } class Person { public string Name { get; set; } public Gender Gender { get; set; } } 这规定了每个人应该是男性或女性(现在我们不讨论性别)。如果我忘记设置Gender属性,那

规则指定所有枚举都应该有一个名为
Unknown
0
值(我们这里不讨论标志)。我理解您希望防止未初始化的值自动获得意义的原因。假设我定义以下枚举:

enum Gender
{
    Male,
    Female
}

class Person
{
    public string Name { get; set; } 
    public Gender Gender { get; set; }
}
这规定了每个人应该是男性或女性(现在我们不讨论性别)。如果我忘记设置
Gender
属性,那么此人将自动成为男性,这可能会导致问题。因此,我理解CA1008警告,因此0值应该保留给未知/未初始化的值

因此,让我们将
Gender
枚举更改为,不再使用0值:

enum Gender
{
    Male = 1,
    Female = 2
}
如果我没有指定性别,那么这个人就不是男性或女性。序列化过程中可能会出现问题。值
0
对于调试期间的枚举不是很有描述性。为了修复它并避免CA1008警告,我再次更改枚举:

enum Gender
{
    Unknown = 0,
    Male = 1,
    Female = 2
}
未初始化的属性现在显示为
Unknown
,看起来不错。但我可能引入了另一个问题,那就是
未知
值看起来像一个有效值,可以应用于用户。我还可能收到关于不处理所有枚举值的警告。假设我使用的构造函数要求我指定性别和名称,以避免未初始化的属性:

public Person(string name, Gender gender)
{
     Name = name ?? throw new ArgumentNullException(name);
     Gender = gender;
}
当我定义
Unknown
枚举时,现在可以显式地将性别设置为
Unknown
。当然,这可以在构造函数中检查,但这只会在运行时发出信号。如果未定义
未知
值,则调用方只能将其设置为男性或女性

修复方法可能是使用可为空的性别属性,因此未初始化的值是显式的
null
值(我们不再定义
Unknown
值)。但是使用可空类型会使编程更加复杂,所以我不建议这样做

obsoletAttribute
应用于
Unknown
值可能是个好主意。当有人显式使用该值时,会将其标记为警告(在构建时)

处理未初始化枚举值的正确方法是什么?使用
obsoletateAttribute
是一个好主意还是有其他缺点

注: *虽然这里的“过时”不是正确的语义,但如果使用了该值,它是生成警告的唯一(简单)方法。
*在没有默认构造函数的情况下使用POCO可能会使序列化复杂化,因此通常不建议在没有它们的情况下使用(可序列化的)类。

枚举的目标是提供表示可能值的命名常量。在您的特定设计中(这是对现实世界的抽象),一个人的性别要么是
男性
,要么是
女性
。没有
None

您的枚举需要一个
0
值成员,因为默认的基础类型是
int
。出于这个原因(正如编译器所指出的),它应该是您的选择之一(
男性
女性
):

需要值这一事实应该由抽象的构造函数强制执行,因为人必须有性别:

public Person(string name, Gender gender)
{
     Name = name ?? throw new ArgumentNullException(name);
     Gender = gender;
}
另一方面,如果您的设计抽象出一个人可以选择而不是提供其性别的世界,那么您可以使用默认值表示:

public enum Gender
{
    NotProvided,  //compiler defaults to 0
    Male,
    Female
}

在这种情况下,有两种类型的构造函数是有意义的:

public Person(string name)
{
     Name = name ?? throw new ArgumentNullException(name);
}

public Person(string name, Gender gender)
{
     Name = name ?? throw new ArgumentNullException(name);
     Gender = gender;
}

换句话说,编译器已经指示了正确的处理。您只需要确保您的抽象实现方式能够正确地表示所建模的内容。

您的问题定义了三个我认为基本上互不兼容的需求:

  • 您希望通过在枚举中使用0值默认值来避免CA1008警告
  • 您希望防止枚举性别的用户能够显式使用未知值
  • 您希望为Person类实现一个默认构造函数
  • 如果满足1和2,则必须引入非默认构造函数来强制初始化。那么3就不能满足了

    如果满足1和3,则必须接受用户可能忘记初始化此属性,并且必须引入一些内容来处理该属性具有有效值但未初始化的情况。在许多情况下,解决方案是将未知默认值作为第三个有效值来实现和处理,但是2不能满足要求

    如果您满足2和3,那么您将遇到一个问题,您必须决定默认情况下性别应初始化为男性还是女性,以同时满足#1。如果使用默认构造函数,当默认选择有一半时间是错误的时,您将遇到问题

    满足所有这三项要求的唯一途径可能是将性别作为一种亚类型的人,而不仅仅是一种财产

    enum性别
    {
    男,,
    女性
    }
    抽象类人
    {
    公共字符串名称{get;set;}
    公开摘要性别{get;}
    }
    类别:人
    {
    公共覆盖性别性别{get{return-Gender.Male;}
    公众罪犯()
    { ... }
    }
    类女性:人
    {
    公共覆盖性别性别{get{return-Gender.Female;}
    公共女性人士()
    { ... }
    }
    
    通过这种方式,您强制用户必须使用男性默认构造函数或女性默认构造函数实例化一个人。 序列化还能够保留子类型并使用默认构造函数,而不会导致错误的默认值

    正在使用“过时”属性
    public Person(string name, Gender gender)
    {
         Name = name ?? throw new ArgumentNullException(name);
         Gender = gender;
    }
    
    public enum Gender
    {
        NotProvided,  //compiler defaults to 0
        Male,
        Female
    }
    
    public enum Gender
    {
        NotProvided = 0
        Male = 1,
        Female = 2
    }
    
    public Person(string name)
    {
         Name = name ?? throw new ArgumentNullException(name);
    }
    
    public Person(string name, Gender gender)
    {
         Name = name ?? throw new ArgumentNullException(name);
         Gender = gender;
    }
    
    abstract class Gender : 
       whatever interfaces you need for serialization and so on
    {
      private Gender() { } // prevent subclassing 
      private class MaleGender : Gender 
      {
        // Serialization code for male gender
      }
      public static readonly Gender Male = new MaleGender();
      // now do it all again for FemaleGender
    }