为什么在枚举内部声明位域组合会产生与在枚举外部声明不同的结果?

Why does declaring a combination of bit fields inside an enum produce a different result than declaring it outside of the enum?

在这里,我有一个由位字段指示的主题列表,"Optional" 字段包含底部的可选主题。

[Flags]
enum Subjects 
{
    Art         = 0b_0000_0001,
    Agriculture = 0b_0000_0010,
    English     = 0b_0000_0100,
    Geography   = 0b_0000_1000,
    Maths       = 0b_0001_0000,
    Science     = 0b_0010_0000,
    Optional    = Art | Agriculture,
}

当我将可选主题打印到控制台时,得到了一个意想不到的结果:

Console.WriteLine(Subjects.Optional); // returns "Optional", I expected "Art, Agriculture"

现在,如果我要在枚举之外声明相同的可选字段并记录它:

// NOTE: I had to comment out the "Optional" field, otherwise it would return Optional once again

var optional = Subjects.Art | Subjects.Agriculture;
Console.WriteLine(optional); // returns "Art, Agriculture" not "Optional"

它按预期工作。

所以我的问题是,当我将组合位域放在枚举中与将它放在枚举外时,为什么我会收到不同的输出?

您可以按以下方式编写枚举声明,得到相同的结果:

[Flags]
enum Subjects 
{
    Art         = 0b_0000_0001,
    Agriculture = 0b_0000_0010,
    English     = 0b_0000_0100,
    Geography   = 0b_0000_1000,
    Maths       = 0b_0001_0000,
    Science     = 0b_0010_0000,
    Optional    = 0b_0000_0011
}

编译器如何知道 Optional 是一个组合字段?当一个字段存在时,它将在 ToString() 方法中被选择。如果你想避免这种情况,你可以删除 Optional 字段并添加扩展方法:

public bool IsOptional(this Subjects subjects)
 {
 return subjects.HasFlag(Subjects.Art) && subjects.HasFlag(Subjects.Agriculture);
 }

或者您可以编写自己的方法将您的枚举转换为字符串,也许使用 Optional 字段获取另一个值

您没有区分枚举值和变量,但它们非常不同。


枚举滥用行为

顺便说一句,我认为您试图将一些关于这些枚举值的额外元数据(即它们是否可选)偷偷放入组合的 Optional 字段中,从而滥用了枚举的目的。

我怀疑对你来说最好的解决方案是完全放弃使用枚举,因为枚举值不应该有更多元数据围绕它们。

我仍然回答了这个问题,因为我对枚举滥用的怀疑完全基于一个名字和我对它对你的意义的解释。由您决定您是想在枚举中偷偷摸摸一些元数据,还是我误解了您的意图。


枚举值

[Flags]
enum Subjects 
{
    Art         = 0b_0000_0001,
    Agriculture = 0b_0000_0010,
    Optional    = Art | Agriculture,
}

当您在枚举中包含组合值时,您将其定义为有效的枚举值。您实际上是在告诉编译器 Subjects.Optional 是枚举的有效(因此有意义)值,暗示可以而且应该使用它。

这导致编译器使用 Subjects.Optional 值(及其字符串表示形式,即 "Optional"),因为您告诉编译器它对您有意义。

变量

[Flags]
enum Subjects 
{
    Art         = 0b_0000_0001,
    Agriculture = 0b_0000_0010
}

var optional = Subjects.Art | Subjects.Agriculture;

重要的是要意识到 optional 是一个 变量 而不是一个枚举值。这里只有两个枚举值,ArtAgriculture.

在这种情况下,您没有将 Optional 定义为枚举值,因此编译器无法使用或引用不存在的枚举值。

因此,它求助于找出哪种枚举值组合会产生(组合的)optional 值,并且它意识到通过组合 Subject.ArtSubject.Agriculture,你得到 optional 描述的值,这就是为什么它 returns 一个逗号分隔的字符串 Art, Agriculture.


如果您想获取逗号分隔的字符串,同时还保留枚举本身中的组合值,您将不得不自己生成逗号分隔的字符串。例如:

public string AsCommaSeparatedString(Subjects myEnum)
{
    var possibleSubjects = new List<Subjects>() { Subjects.Art, Subjects.Agriculture };

    var subjects = possibleSubjects.Where(possibleSubject => myEnum.HasFlag(possibleSubject));

    return String.Join(",", names);
}

你必须列出所有你想要包含的枚举值(所以像 Optional 这样的其他值将被忽略),但是当你特别想排除一些值时这是不可避免的(比如 Optional) 免于被提及。