我不时会看到如下的枚举:
[Flags] public enum Options { None = 0, Option1 = 1, Option2 = 2, Option3 = 4, Option4 = 8 }
我不明白[Flags]
-attribute 到底是做什么的.
任何人都可以发布一个很好的解释或示例?
[Flags]
只要enumerable表示可能值的集合而不是单个值,就应该使用该属性.此类集合通常与按位运算符一起使用,例如:
var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;
请注意,该[Flags]
属性本身不会启用它 - 它所做的只是通过该.ToString()
方法允许一个很好的表示:
enum Suits { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 } [Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 } ... var str1 = (Suits.Spades | Suits.Diamonds).ToString(); // "5" var str2 = (SuitsFlags.Spades | SuitsFlags.Diamonds).ToString(); // "Spades, Diamonds"
同样重要的是要注意,[Flags]
不会自动使枚举值为2的幂.如果省略数值,则枚举将不会像在按位运算中所期望的那样工作,因为默认情况下,值以0和递增开头.
声明不正确:
[Flags] public enum MyColors { Yellow, // 0 Green, // 1 Red, // 2 Blue // 3 }
如果以这种方式声明,则值将为Yellow = 0,Green = 1,Red = 2,Blue = 3.这将使其无法用作标志.
这是一个正确声明的例子:
[Flags] public enum MyColors { Yellow = 1, Green = 2, Red = 4, Blue = 8 }
要检索属性中的不同值,可以执行以下操作:
if (myProperties.AllowedColors.HasFlag(MyColor.Yellow)) { // Yellow is allowed... }
或者在.NET 4之前:
if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow) { // Yellow is allowed... } if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green) { // Green is allowed... }
在封面下
这是有效的,因为您在枚举中使用了2的幂.在封面下,您的枚举值在二进制1和零中看起来像这样:
Yellow: 00000001 Green: 00000010 Red: 00000100 Blue: 00001000
同样,在使用二进制按位OR 运算符将属性AllowedColors设置为Red,Green和Blue之后|
,AllowedColors看起来像这样:
myProperties.AllowedColors: 00001110
因此,当您检索值时,您实际上对值执行按位AND &
:
myProperties.AllowedColors: 00001110 MyColor.Green: 00000010 ----------------------- 00000010 // Hey, this is the same as MyColor.Green!
None = 0值
关于0
枚举中的使用,请引用MSDN:
[Flags] public enum MyColors { None = 0, .... }
使用None作为其值为零的标志枚举常量的名称.您不能在按位AND运算中使用None枚举常量来测试标志,因为结果始终为零.但是,您可以在数值和None枚举常量之间执行逻辑而非按位比较,以确定是否设置了数值中的任何位.
你可以找到更多信息有关标志属性及其在使用MSDN和MSDN上的标志设计
你也可以这样做
[Flags] public enum MyEnum { None = 0, First = 1 << 0, Second = 1 << 1, Third = 1 << 2, Fourth = 1 << 3 }
我发现位移比输入4,8,16,32更容易,等等.它对您的代码没有影响,因为它们都是在编译时完成的
结合答案/sf/ask/17360801/(通过位移声明)和/sf/ask/17360801/(使用声明中的组合)您可以对以前的值进行位移而不是使用数字.不一定推荐它,但只是指出你可以.
而不是:
[Flags] public enum Options : byte { None = 0, One = 1 << 0, // 1 Two = 1 << 1, // 2 Three = 1 << 2, // 4 Four = 1 << 3, // 8 // combinations OneAndTwo = One | Two, OneTwoAndThree = One | Two | Three, }
你可以申报
[Flags] public enum Options : byte { None = 0, One = 1 << 0, // 1 // now that value 1 is available, start shifting from there Two = One << 1, // 2 Three = Two << 1, // 4 Four = Three << 1, // 8 // same combinations OneAndTwo = One | Two, OneTwoAndThree = One | Two | Three, }
使用LinqPad确认:
foreach(var e in Enum.GetValues(typeof(Options))) { string.Format("{0} = {1}", e.ToString(), (byte)e).Dump(); }
结果是:
None = 0 One = 1 Two = 2 OneAndTwo = 3 Three = 4 OneTwoAndThree = 7 Four = 8
请参阅以下示例,其中显示了声明和潜在用法:
namespace Flags { class Program { [Flags] public enum MyFlags : short { Foo = 0x1, Bar = 0x2, Baz = 0x4 } static void Main(string[] args) { MyFlags fooBar = MyFlags.Foo | MyFlags.Bar; if ((fooBar & MyFlags.Foo) == MyFlags.Foo) { Console.WriteLine("Item has Foo flag set"); } } } }
我最近问过类似的事情.
如果使用标记,则可以向枚举添加扩展方法,以便更轻松地检查包含的标记(有关详细信息,请参阅post)
这允许你这样做:
[Flags] public enum PossibleOptions : byte { None = 0, OptionOne = 1, OptionTwo = 2, OptionThree = 4, OptionFour = 8, //combinations can be in the enum too OptionOneAndTwo = OptionOne | OptionTwo, OptionOneTwoAndThree = OptionOne | OptionTwo | OptionThree, ... }
然后你可以这样做:
PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree if( opt.IsSet( PossibleOptions.OptionOne ) ) { //optionOne is one of those set }
我发现这比检查包含的标志的大多数方法更容易阅读.
在接受答案的扩展中,在C#7中,可以使用二进制文字来编写枚举标志:
[Flags] public enum MyColors { None = 0b0000, Yellow = 0b0001, Green = 0b0010, Red = 0b0100, Blue = 0b1000 }
我想,这表示清楚的标志是如何工作在幕后.
@Nidonocu
要向现有值集添加另一个标志,请使用OR赋值运算符.
Mode = Mode.Read; //Add Mode.Write Mode |= Mode.Write; Assert.True(((Mode & Mode.Write) == Mode.Write) && ((Mode & Mode.Read) == Mode.Read)));
要添加Mode.Write
:
Mode = Mode | Mode.Write;
关于if ((x & y) == y)...
构造,我有些过于冗长,特别是如果x
AND y
都是复合的标志集,你只想知道是否有任何重叠.
在这种情况下,您真正需要知道的是在您进行了位掩码之后是否存在非零值[1].
[1]见Jaime的评论.如果我们是真正的位掩码,我们只需要检查结果是否为正.但由于
enum
可以均为负数,甚至是奇怪的是,当联合的[Flags]
属性,它的防守为代码!= 0
而不是> 0
.
建立@ andnil的设置......
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace BitFlagPlay { class Program { [Flags] public enum MyColor { Yellow = 0x01, Green = 0x02, Red = 0x04, Blue = 0x08 } static void Main(string[] args) { var myColor = MyColor.Yellow | MyColor.Blue; var acceptableColors = MyColor.Yellow | MyColor.Red; Console.WriteLine((myColor & MyColor.Blue) != 0); // True Console.WriteLine((myColor & MyColor.Red) != 0); // False Console.WriteLine((myColor & acceptableColors) != 0); // True // ... though only Yellow is shared. Console.WriteLine((myColor & MyColor.Green) != 0); // Wait a minute... ;^D Console.Read(); } } }
使用标志时,我经常声明其他“无”和“所有”项。这些有助于检查是否设置了所有标志或没有设置标志。
[Flags] enum SuitsFlags { None = 0, Spades = 1 << 0, Clubs = 1 << 1, Diamonds = 1 << 2, Hearts = 1 << 3, All = ~(~0 << 4) }
用法:
Spades | Clubs | Diamonds | Hearts == All // true Spades & Clubs == None // true
更新2019-10:
从C#7.0开始,您可以使用二进制文字,阅读起来可能更直观:
[Flags] enum SuitsFlags { None = 0b0000, Spades = 0b0001, Clubs = 0b0010, Diamonds = 0b0100, Hearts = 0b1000, All = 0b1111 }
标志允许您在枚举中使用位掩码.这允许您组合枚举值,同时保留指定的枚举值.
[Flags] public enum DashboardItemPresentationProperties : long { None = 0, HideCollapse = 1, HideDelete = 2, HideEdit = 4, HideOpenInNewWindow = 8, HideResetSource = 16, HideMenu = 32 }