8

编辑:似乎大多数人误解了我的问题。

我知道枚举是如何工作的,而且我知道二进制。我想知道为什么带有 [Flags] 属性的枚举是这样设计的。

原帖:

这可能是重复的,但我没有找到任何其他帖子,所以这里。

我敢打赌,它背后有一些很好的理由,我只是觉得它有点容易出错。

[Flag]
public enum Flagged
{
  One, // 0
  Two, // 1
  Three, // 2
  Four, // 3
}

Flagged f; // Defaults to Flagged.One = 0
f = Flagged.Four;
(f & Flagged.One) != 0; // Sure.. One defaults to 0
(f & Flagged.Two) != 0; // 3 & 1 == 1
(f & Flagged.Three) != 0; // 3 & 2 == 2

如果它做这样的事情不是更有意义吗?

[Flag]
public enum Flagged
{
  One = 1 << 0, // 1
  Two = 1 << 1, // 2
  Three = 1 << 2, // 4
  Four = 1 << 3, // 8
}

Flagged f; // Defaults to 0
f = Flagged.Four;
(f & Flagged.One) != 0; // 8 & 1  == 0
(f & Flagged.Two) != 0; // 8 & 2 == 0
(f & Flagged.Three) != 0; // 8 & 4 == 0
(f & Flagged.Four) != 0; // 8 & 8 == 8

当然..我不太确定它应该如何处理这样的自定义标志

[Flag]
public enum Flagged
{
   One, // 1
   Two, // 2
   LessThanThree = One | Two,
   Three, // 4? start from Two?
   LessThanFour = Three | LessThanThree,
   Three, // 8? start from Three?
}

该规范给出了一些指导方针

以 2 的幂定义枚举常数,即 1、2、4、8 等。这意味着组合枚举常量中的各个标志不重叠。

但这也许应该自动完成,因为我敢打赌你永远不会希望我的第一个例子出现。请赐教:)

4

6 回答 6

13

Flags属性仅用于将值格式化为多个值。位操作适用于有或没有属性的基础类型。

于 2010-06-24T17:10:44.620 回答
7

除非明确给出其他值,否则枚举的第一项为零。为标志枚举设置零值通常是最佳实践,因为它为零值提供了语义含义,例如“无标志”或“关闭”。这有助于维护代码,因为它可以暗示代码中的意图(尽管注释也实现了这一点)。

除此之外,是否需要零值真的取决于您和您的设计。

由于标志枚举仍然只是枚举(FlagsAttribute仅指示调试器将值解释为其他值的组合),因此枚举中的下一个值总是比前一个值多一个。因此,您应该明确指定位值,因为您可能希望将组合表示为其他值的按位或。

也就是说,可以想象标志枚举的语法要求所有按位组合放在枚举定义的末尾或以某种方式标记,以便编译器知道如何处理其他所有内容。

例如(假设一个flags关键字并且我们在北半球),

flags enum MyFlags
{
   January,
   February,
   March,
   April,
   May,
   June,
   July,
   August,
   September,
   October,
   November,
   December,
   Winter = January | February | March
   Spring = April | May | June
   Summer = July | August | September
   Autumn = October | November | December
} 

使用这种语法,编译器可以自己创建 0 值,并自动将标志分配给其他值。

于 2010-06-24T17:05:27.613 回答
6

该属性是 [Flags] 而不是 [Flag],它没有什么神奇之处。它似乎唯一影响的是 ToString 方法。指定 [Flags] 时,值以逗号分隔。您可以指定值以使其在位字段中使用有效。

于 2010-06-24T17:13:01.940 回答
4

带注释的 C# 3 规范中没有任何内容。我认为带注释的 C# 4 规范中可能有一些东西——我不确定。(我想我自己开始写这样的注释,但后来删除了它。)

这对于简单的情况很好,但是一旦你开始添加额外的标志,它就会变得有点棘手:

[Flags]
enum Features
{
    Frobbing, // 1
    Blogging, // 2 
    Widgeting, // 4
    BloggingAndWidgeting = Frobbing | Blogging, // 6
    Mindnumbing // ?
}

Mindnumbing应该有什么价值?下一个未使用的位?如果你设置一个固定的整数值呢?

我同意这是一种痛苦。也许可以制定出一些合理的规则……但我想知道复杂性与价值的平衡是否真的会奏效。

于 2010-06-24T17:12:49.553 回答
2

简单地说,Flags就是一个属性。它在创建枚举之后才适用,因此不会更改分配给枚举的值。

话虽如此,MSDN 页面Designing Flags Enumerations说:

对标志枚举的值使用 2 的幂,以便可以使用按位或运算自由组合它们。

重要提示:如果您不使用 2 的幂或 2 的幂的组合,则按位运算将无法按预期工作。

同样,页面FlagsAttribute

以 2 的幂定义枚举常数,即 1、2、4、8 等。这意味着组合枚举常量中的各个标志不重叠。

于 2010-06-24T18:20:02.617 回答
0

在 C 中,可以(ab)使用预处理器自动生成二次幂枚举。如果一个宏 make_things 扩展为“make_thing(flag1) make_thing(flag2) make_thing(flag3)”等,则可以使用不同的 make_thing 定义多次调用该宏,以实现二次幂标志名称的序列以及其他一些好东西。

例如,首先将 make_thing(x) 定义为“LINEAR_ENUM_##x”(包括逗号),然后使用枚举语句生成枚举列表(包括 make_things 宏之外的 LINEAR_NUM_ITEMS)。然后创建另一个枚举,这次将 make_thing(x) 定义为 "FLAG_ENUM_##x = 1<

相当漂亮的一些可以通过这种方式完成的事情,标志和线性值自动保持同步;代码可以做一些不错的事情,比如“if (thingie[LINEAR_ENUM_foo] thing_errors |= FLAG_ENUM_foo;”(同时使用线性和标志值)。不幸的是,我不知道在 C# 或 VB.net 中可以做任何远程类似的事情。

于 2010-06-24T18:30:27.320 回答