C# 为什么在枚举内声明位字段的组合会产生与在枚举外声明不同的结果?

C# 为什么在枚举内声明位字段的组合会产生与在枚举外声明不同的结果?,c#,enums,bit-fields,bitwise-or,C#,Enums,Bit Fields,Bitwise Or,这里,我有一个由位字段表示的主题列表,底部有一个包含可选主题的“可选”字段 [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_0

这里,我有一个由位字段表示的主题列表,底部有一个包含可选主题的“可选”字段

[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
}
编译器如何知道
可选
是一个组合字段?当字段存在时,将在
ToString()
方法中选择该字段。如果要避免这种情况,可以删除
可选
字段并添加扩展方法:

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

或者,您可以编写自己的方法将枚举转换为字符串,也可以使用获取
可选
字段的另一个值您没有区分枚举值和变量,但它们非常不同


枚举滥用 另一方面,我认为您试图将有关这些枚举值的一些额外元数据(即它们是否可选)潜入组合的
optional
字段中,这是滥用枚举的目的

我认为最好的解决方案是完全不使用枚举,因为枚举值周围不应该有更多的metada

我仍然回答了这个问题,因为我对enum滥用的怀疑完全是基于一个名称以及我对其含义的解释。这取决于您是否试图在枚举中隐藏一些元数据,还是我误解了您的意图


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

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

变量 必须认识到,
可选
是一个变量,而不是枚举值。这里只有两个枚举值,
Art
Agriculture

在本例中,您没有将
Optional
定义为枚举值,因此编译器无法使用或引用不存在的枚举值

因此,它需要找出哪个枚举值组合会产生(组合的)
optional
值,并意识到通过组合
Subject.Art
Subject.Agriculture
,可以得到
optional
描述的值,这就是它返回逗号分隔字符串
Art的原因,农业


如果要获取逗号分隔字符串,同时在枚举本身中保留组合值,则必须自己生成逗号分隔字符串。例如:

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);
}
public string ascommamseparatedstring(Subjects myEnum)
{
var possibleSubjects=new List(){Subjects.Art,Subjects.Agriculture};
var subjects=possibleSubjects.Where(possibleSubject=>myEnum.hasvag(possibleSubject));
返回字符串。Join(“,”名称);
}

您必须列出要包含的所有枚举值(因此其他值(如
可选的
)将被忽略),但当您特别想排除某些值(如
可选的
)时,这是不可避免的。

“我必须注释掉“可选”字段”以查看哪个字段描述了行为。似乎没有指定“始终首选分解为标志”的格式。请尝试以下操作:Console.WriteLine(((uint)可选).ToString(“X8”);请注意,“编译器如何知道可选字段是一个组合字段?”可能不是解决OP期望的最佳方法,因为具有多个
1
位的标志枚举值通常被理解为具有单个
1
位的标志枚举值的组合。虽然这并不一定适用于所有可能的场景,但这是标志枚举的默认行为。
[Flags]
enum Subjects 
{
    Art         = 0b_0000_0001,
    Agriculture = 0b_0000_0010
}

var optional = Subjects.Art | Subjects.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);
}