C#中的[Flags]枚举属性是什么意思?


1445

我不时看到如下枚举:

[Flags]
public enum Options 
{
    None    = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}

我不明白该[Flags]属性的确切作用。

任何人都可以发表很好的解释或榜样吗?


除了已接受的答案外,还值得注意的是VB.NET实际上需要 [Flags]-至少根据.NET 专家的说法
Rushyo 2012年

8
注意,这些天在VB中不是必需的。将行为另存为C#-仅更改ToString()输出。注意,您也可以在枚举本身内进行逻辑或。很酷。猫= 1,狗= 2,CatAndDog =猫|| 狗。
Chalky

14
@Chalky我的意思是CatAndDog = Cat | Dog(逻辑或而不是条件)。
DdW

5
@DdW,部分正确:应该使用,但是| 被称为二进制或。II是逻辑“或”(允许短路):至少根据Microsoft
所说

Answers:


2152

[Flags]只要可枚举表示可能值的集合而不是单个值,就应使用该属性。此类集合通常与按位运算符一起使用,例如:

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和0:

 Yellow: 00000001
 Green:  00000010
 Red:    00000100
 Blue:   00001000

同样,使用二进制按位或运算符将属性AllowedColors设置为红色,绿色和蓝色后|AllowedColors如下所示:

myProperties.AllowedColors: 00001110

因此,当您检索该值时,您实际上是在&对这些值执行按位与运算:

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Hey, this is the same as MyColor.Green!

无= 0值

关于0在您的枚举中的使用,引自MSDN:

[Flags]
public enum MyColors
{
    None = 0,
    ....
}

使用None作为值为零的标志枚举常量的名称。您不能在按位与运算中使用None枚举常量来测试标志,因为结果始终为零。但是,您可以在数值和None枚举常量之间进行逻辑比较而不是按位比较,以确定是否设置了数值中的任何位。

您可以在msdn上找到有关flags属性及其用法以及在msdn上设计标志的更多信息


156
标志本身不执行任何操作。同样,C#本身不需要标志。但是,ToString你的枚举的实现使用标志,也是如此Enum.IsDefinedEnum.Parse等尝试MyColor.Yellow的结果,除去标志和外观| MyColor.Red; 没有它,您将获得“ 5”,带有标志的您将获得“黄色,红色”。框架的其他一些部分也使用[Flags](例如XML序列化)。
鲁宾

7
//已设置黄色...,我有点误解。没有设置任何内容,这意味着Yellow是您AllowedColors的成员,也许更好的是// Yellow是否被允许?
Scott Weaver 2012年

48
我更喜欢使用以下形式的常量:A = 1 << 0,B = 1 << 1,C = 1 << 2 ...更容易阅读,理解,直观地检查和更改。
Nick Westgate

2
@borrrden,是的,是的!我发现了这一点:msdn.microsoft.com/en-us/library/system.enum.aspx- 请参阅“备注”部分:“枚举是.NET Framework中所有枚举的基类。” 和“枚举没有显式继承自Enum;继承关系由编译器隐式处理。” 所以,当您编写:public enum bla bla bla-这是一个值类型。但是,HasFlag方法希望您给他一个System.Enum实例,它是一个类(引用类型:)
Aleksei Chepovoi

2
如果要从枚举中排除标志,请使用xor(在C#中为^)。所以如果你有myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;。您可以这样做:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
乔什·诺

778

您也可以这样做

[Flags]
public enum MyEnum
{
    None   = 0,
    First  = 1 << 0,
    Second = 1 << 1,
    Third  = 1 << 2,
    Fourth = 1 << 3
}

我发现移位比键入4,8,16,32等更容易。它对代码没有影响,因为它们都是在编译时完成的


18
这就是我的意思,我更喜欢在源代码中包含完整的int。如果我在数据库的一个名为MyEnum的表上有一列,其中存储了一个枚举的值,并且一条记录中包含131,072,则我需要拿出计算器来找出与值1的枚举相对应的值。 << 17。与仅看到源中写入的值131,072相反。
JeremyWeir

6
@jwg我同意,过分担心它的运行时性能是愚蠢的,但是尽管如此,我还是很高兴知道这不会在您使用枚举的任何位置插入位移。更多的是“整洁”的东西,而不是与性能相关的任何东西
Orion Edwards

18
@JeremyWeir-将在标志枚举值中设置几个位。因此,您的数据分析方法是不正确的。运行一个过程以二进制表示您的整数值。131,072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] ..通过设置第17位,可以轻松确定分配为1 << 17的枚举值。0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] ..枚举值分配1 << 31、1 << 30、1 << 26、1 << 25 ..等完全没有计算器的帮助..我怀疑您在没有二进制表示的情况下是否能够确定所有信息。
Brett Caswell 2013年

13
另外要指出的是,您可以从“上一个”枚举值移位,而不是直接从数字移位,即,Third = Second << 1而不是Third = 1 << 2-请参阅下面的更完整说明
-drzaus

26
我喜欢这个,但一旦你到了底层的枚举类型的上限,编译器不警惕位的变化,如1 << 31 == -21474836481 << 32 == 11 << 33 == 2,等。相反,如果您说ThirtySecond = 2147483648一个int类型的枚举,则编译器将引发错误。
aaaantoine 2014年

115

结合答案https://stackoverflow.com/a/8462/1037948(通过位移进行声明)和https://stackoverflow.com/a/9117/1037948(在声明中使用组合),您可以将以前的值进行位移而不是使用数字。不一定推荐它,只是指出您可以。

而不是:

[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

23
组合是一个不错的建议,但是我认为链接的移位容易产生复制和粘贴错误,例如2 = 1 << 1,3 = 1 << 1,等等。。。形式1 << n更安全,意图更明确。
Rupert Rawnsley 2014年

2
@RupertRawnsley引用我的回答:>不一定推荐它,但只是指出您可以
drzaus 2014年

49

请参见以下示例,以显示声明和潜在用法:

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");
            }
        }
    }
}

即使您忽略[Flags],此示例也有效。这是我要学习的[标志]部分。
gbarry


37

最近问类似的事情。

如果使用标志,则可以向枚举添加扩展方法,以更轻松地检查包含的标志(有关详细信息,请参见文章)

这使您可以执行以下操作:

[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
}

我发现这比大多数检查包含标志的方法更容易阅读。


IsSet是我假设的扩展方法吗?
罗伯特·麦克莱恩

是的,请阅读我链接到的其他问题以获取详细信息:stackoverflow.com/questions/7244
Keith,2009年

71
.NET 4 HasFlag在枚举中添加了一个方法,因此您opt.HasFlag( PossibleOptions.OptionOne )无需编写自己的扩展即可
Orion Edwards 2010年

1
请注意,HasFlag慢得多比做位运算。
Wai Ha Lee

1
@WaiHaLee您可以使用CodeJam.EnumHelper.IsFlagSet扩展方法,该方法使用Expression编译为快速版本:github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_19年

22

@Nidonocu

要将另一个标志添加到现有值集中,请使用OR赋值运算符。

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));

18

使用标志时,我经常声明其他“无”和“所有”项。这些有助于检查是否设置了所有标志或没有设置标志。

[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

}


14

关于该if ((x & y) == y)...构造,我有些过于冗长,特别是如果xAND y都是复合标志集,而您只想知道是否存在任何重叠。

在这种情况下,您真正​​需要知道的是在bitmasked之后是否存在非零值[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();
        }
    }
}

枚举可能基于带符号的类型,因此您应使用“!= 0”而不是“> 0”。
乌鸦

@JaimePardos-正如我们在此示例中所做的那样,只要我们保持诚实的字节数,就没有否定的概念。只能是0到255。如MSDN所警告,“如果将负数定义为标志枚举常量,请谨慎使用,因为[...]可能会使您的代码令人困惑,并鼓励编码错误。” 用“负位标志”来思考是很奇怪的!; ^)我会编辑更多内容。但是您是对的,如果我们确实在负数中使用enum,则需要检查!= 0
鲁芬

10

标志允许您在枚举中使用位掩码。这使您可以合并枚举值,同时保留指定的值。

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}

0

抱歉,如果有人已经注意到这种情况。我们可以在反射中看到标志的一个完美示例。是绑定标志ENUM。 https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.bindingflags?view=netframework-4.8

[System.Flags]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public enum BindingFlags

用法

             // BindingFlags.InvokeMethod
            // Call a static method.
            Type t = typeof (TestClass);

            Console.WriteLine();
            Console.WriteLine("Invoking a static method.");
            Console.WriteLine("-------------------------");
            t.InvokeMember ("SayHello", BindingFlags.InvokeMethod | BindingFlags.Public | 
                BindingFlags.Static, null, null, new object [] {});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.