[Flags]枚举属性在C#中意味着什么?

Bri*_*ahy 1383 c# enums flags

我不时会看到如下的枚举:

[Flags]
public enum Options 
{
    None    = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}
Run Code Online (Sandbox Code Playgroud)

我不明白[Flags]-attribute 到底是做什么的.

任何人都可以发布一个很好的解释或示例?

and*_*nil 2076

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

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;
Run Code Online (Sandbox Code Playgroud)

请注意,该[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"
Run Code Online (Sandbox Code Playgroud)

同样重要的是要注意,[Flags] 不会自动使枚举值为2的幂.如果省略数值,则枚举将不会像在按位运算中所期望的那样工作,因为默认情况下,值以0和递增开头.

声明不正确:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}
Run Code Online (Sandbox Code Playgroud)

如果以这种方式声明,则值将为Yellow = 0,Green = 1,Red = 2,Blue = 3.这将使其无法用作标志.

这是一个正确声明的例子:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}
Run Code Online (Sandbox Code Playgroud)

要检索属性中的不同值,可以执行以下操作:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow is allowed...
}
Run Code Online (Sandbox Code Playgroud)

或者在.NET 4之前:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow is allowed...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green is allowed...
}    
Run Code Online (Sandbox Code Playgroud)

在封面下

这是有效的,因为您在枚举中使用了2的幂.在封面下,您的枚举值在二进制1和零中看起来像这样:

 Yellow: 00000001
 Green:  00000010
 Red:    00000100
 Blue:   00001000
Run Code Online (Sandbox Code Playgroud)

同样,在使用二进制按位OR 运算符将属性AllowedColors设置为Red,Green和Blue之后|,AllowedColors看起来像这样:

myProperties.AllowedColors: 00001110
Run Code Online (Sandbox Code Playgroud)

因此,当您检索值时,您实际上对值执行按位AND &:

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Hey, this is the same as MyColor.Green!
Run Code Online (Sandbox Code Playgroud)

None = 0值

关于0枚举中的使用,请引用MSDN:

[Flags]
public enum MyColors
{
    None = 0,
    ....
}
Run Code Online (Sandbox Code Playgroud)

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

你可以找到更多信息有关标志属性及其在使用MSDNMSDN上的标志设计

  • 旗帜本身什么都不做.此外,C#本身不需要Flags.但是你的枚举的`ToString`实现使用了Flags,`Enum.IsDefined`,`Enum.Parse`等也是如此.尝试删除Flags并查看MyColor的结果.MyColor.Red; 没有它你得到"5",标志你得到"黄色,红色".框架的其他一些部分也使用[Flags](例如,XML Serialization). (149认同)
  • 我更喜欢使用形式A = 1 << 0,B = 1 << 1,C = 1 << 2 ...的常量...更容易阅读,理解,视觉检查和改变. (45认同)
  • //黄色已经设定......,我觉得有点误导.没有设置,这意味着Yellow是你的AllowedColors的成员,也许更好的是//允许黄色? (7认同)
  • 如果要从枚举中排除标志,请使用 xor,即 C# 中的 ^。所以如果你有`myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;`。你可以这样做:`myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green` (3认同)
  • @borrrden,是的,是的!我发现了这一点:http://msdn.microsoft.com/zh-cn/library/system.enum.aspx-请参见“备注”部分:“枚举是.NET Framework中所有枚举的基类。” 和“枚举没有显式继承自Enum;继承关系由编译器隐式处理。” 因此,当您编写:public enum bla bla bla-这是一个值类型。但是,HasFlag方法要您给他一个System.Enum实例,它是一个类(引用类型:) (2认同)

Ori*_*rds 760

你也可以这样做

[Flags]
public enum MyEnum
{
    None   = 0,
    First  = 1 << 0,
    Second = 1 << 1,
    Third  = 1 << 2,
    Fourth = 1 << 3
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我喜欢这个,但是一旦达到底层枚举类型的上限,编译器就不会警告位移,例如`1 << 31 == -2147483648`,`1 << 32 == 1`, `1 << 33 == 2`,依此类推.相反,如果对int类型枚举说"ThirtySecond = 2147483648",则编译器会抛出错误. (24认同)
  • 这就是我的意思,我更喜欢在源代码中使用完整的int.如果我在数据库中的一个名为MyEnum的表上有一个列,它存储了一个枚举的值,而一个记录有131,072,我需要拿出我的计算器来计算出对应于值为1的枚举的<< 17.而不是只看到源中写的值131,072. (17认同)
  • @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 ..等等,甚至没有确定所有的计算器的帮助..我怀疑你能够在没有得到二进制代表的情况下完全确定. (17认同)
  • 同时指出你可以从"之前的"枚举值而不是直接从数字中移位,即`Third = Second << 1`而不是`Third = 1 << 2` - 参见[更完整的描述如下](http ://stackoverflow.com/a/16591032/1037948) (13认同)
  • @jwg我同意,过分担心它的运行时性能是愚蠢的,但同样,我觉得很高兴知道这不会在你使用枚举的任何地方插入位移.更多的是"那个整洁"的东西而不是与表现有关的任何东西 (6认同)
  • "这一切都是在编译时完成的"这种类型的评论可能对某些人感兴趣,但我发现它令人沮丧,因为它暗示说,在运行时20位移位将对性能产生任何可衡量的影响.CPU在位移方面非常擅长,枚举不会有任意数量的成员,无论如何这对于超过32或64个元素都不起作用.作为性能问题并不重要. (5认同)
  • 只是为了添加另一个C#7的新选项,你可以使用`0b1`,`0b10`等(或者如果你喜欢所有排列正确的话,可以使用`0b00000001`!) (3认同)

drz*_*aus 111

结合答案/sf/answers/592371/(通过位移声明)和/sf/answers/638221/(使用声明中的组合)您可以对以前的值进行位移而不是使用数字.不一定推荐它,但只是指出你可以.

而不是:

[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,
}
Run Code Online (Sandbox Code Playgroud)

你可以申报

[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,
}
Run Code Online (Sandbox Code Playgroud)

使用LinqPad确认:

foreach(var e in Enum.GetValues(typeof(Options))) {
    string.Format("{0} = {1}", e.ToString(), (byte)e).Dump();
}
Run Code Online (Sandbox Code Playgroud)

结果是:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
Run Code Online (Sandbox Code Playgroud)

  • 这些组合是一个很好的推荐,但我认为链式位移会容易出现复制和粘贴错误,例如Two = One << 1,Three = One << 1等等...增量整数表格1 << n更安全,意图更清晰. (22认同)
  • @RupertRawnsley引用我的答案:&gt;不一定推荐它,但只要指出您可以 (2认同)

OJ.*_*OJ. 48

请参阅以下示例,其中显示了声明和潜在用法:

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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 即使您省略 [Flags],此示例仍然有效。这是我想要了解的 [Flags] 部分。 (2认同)

Kei*_*ith 36

最近问过类似的事情.

如果使用标记,则可以向枚举添加扩展方法,以便更轻松地检查包含的标记(有关详细信息,请参阅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,
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

if( opt.IsSet( PossibleOptions.OptionOne ) ) {
    //optionOne is one of those set
}
Run Code Online (Sandbox Code Playgroud)

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

  • .NET 4为枚举添加了一个`HasFlag`方法,因此您可以执行`opt.HasFlag(PossibleOptions.OptionOne)`而无需编写自己的扩展 (70认同)

Tho*_*sen 32

在接受答案的扩展中,在C#7中,可以使用二进制文字来编写枚举标志:

[Flags]
public enum MyColors
{
    None   = 0b0000,
    Yellow = 0b0001,
    Green  = 0b0010,
    Red    = 0b0100,
    Blue   = 0b1000
}
Run Code Online (Sandbox Code Playgroud)

我想,这表示清楚的标志是如何工作在幕后.

  • 在 C# 7.2 中,使用[前导分隔符](https://docs.microsoft.com/it-it/dotnet/csharp/language-reference/proposals/csharp-7.2/leading-separator) 更加清晰!`0b_0100` (10认同)

ste*_*e_c 22

@Nidonocu

要向现有值集添加另一个标志,请使用OR赋值运算符.

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));
Run Code Online (Sandbox Code Playgroud)


Dav*_*ier 17

要添加Mode.Write:

Mode = Mode | Mode.Write;
Run Code Online (Sandbox Code Playgroud)

  • 或模式| = Mode.Write (57认同)

ruf*_*fin 13

关于if ((x & y) == y)...构造,我有些过于冗长,特别是如果xAND 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Jps*_*psy 13

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

[Flags] 
enum SuitsFlags { 

    None =     0,

    Spades =   1 << 0, 
    Clubs =    1 << 1, 
    Diamonds = 1 << 2, 
    Hearts =   1 << 3,

    All =      ~(~0 << 4)

}
Run Code Online (Sandbox Code Playgroud)

用法:

Spades | Clubs | Diamonds | Hearts == All  // true
Spades & Clubs == None  // true
Run Code Online (Sandbox Code Playgroud)

 
更新2019-10:

从C#7.0开始,您可以使用二进制文字,阅读起来可能更直观:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

    Spades =   0b0001, 
    Clubs =    0b0010, 
    Diamonds = 0b0100, 
    Hearts =   0b1000,

    All =      0b1111

}
Run Code Online (Sandbox Code Playgroud)


小智 12

定义问题

\n

让\xe2\x80\x99s 定义一个表示用户类型的枚举:

\n
public enum UserType\n{\n    Customer = 1,\n    Driver = 2,  \n    Admin = 3,\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们定义包含三个值的 UserType 枚举:Customer, Driver, and Admin.

\n

但是如果我们需要表示一组值怎么办?

\n

例如,在一家送货公司,我们知道管理员和司机都是员工。那么让\xe2\x80\x99s添加一个新的枚举项Employee。稍后,我们将向您展示如何用它代表管理员和驱动程序:

\n
public enum UserType\n{   \n    Customer = 1,\n    Driver = 2,  \n    Admin = 3,\n    Employee = 4\n}\n
Run Code Online (Sandbox Code Playgroud)\n

定义并声明 Flags 属性

\n

Flags 是一个属性,它允许我们将枚举表示为值的集合 \xe2\x80\x8b\xe2\x80\x8 而不是单个值。那么,让\xe2\x80\x99s 看看我们如何在枚举上实现 Flags 属性:

\n
[Flags]\npublic enum UserType\n{   \n    Customer = 1,\n    Driver = 2,  \n    Admin = 4,\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们添加Flags属性并用 2 的幂对值进行编号。如果没有两者,这将无法工作。

\n

现在回到我们之前的问题,我们可以Employee使用|运算符来表示:

\n
var employee = UserType.Driver | UserType.Admin;\n
Run Code Online (Sandbox Code Playgroud)\n

另外,我们可以将其定义为枚举内部的常量以直接使用它:

\n
[Flags]\npublic enum UserType\n{                \n    Customer = 1,             \n    Driver = 2,               \n    Admin = 4,                \n    Employee = Driver | Admin\n}\n
Run Code Online (Sandbox Code Playgroud)\n

幕后花絮

\n

为了更好地理解该Flags属性,我们必须回到数字的二进制表示形式。例如,我们可以将 1 表示为二进制0b_0001,2 表示为0b_0010

\n
[Flags]\npublic enum UserType\n{\n    Customer = 0b_0001,\n    Driver = 0b_0010,\n    Admin = 0b_0100,\n    Employee = Driver | Admin, //0b_0110\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以看到每个值都以有效位表示。这就是 \xe2\x80\x8b\xe2\x80\x8b 用 2 的幂对值 \xe2\x80\x8b\xe2\x80\x8b 进行编号的想法的来源。我们还可以注意到Employee包含两个活动位,即它是 Driver 和 Admin 两个值的组合。

\n

对 Flags 属性的操作

\n

我们可以使用按位运算符来处理Flags.

\n

初始化一个值

\n

对于初始化,我们应该使用名为 None 的值 0,这意味着集合是空的:

\n
[Flags]\npublic enum UserType\n{\n    None = 0,\n    Customer = 1,\n    Driver = 2,\n    Admin = 4,\n    Employee = Driver | Admin\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在,我们可以定义一个变量:

\n
var flags = UserType.None;\n
Run Code Online (Sandbox Code Playgroud)\n

添加一个值

\n

我们可以通过使用|运算符来增加值:

\n
flags |= UserType.Driver;\n
Run Code Online (Sandbox Code Playgroud)\n

现在,flags 变量等于 Driver。

\n

删除一个值

\n

我们可以通过使用运算符删除值&, ~

\n
flags &= ~UserType.Driver;\n
Run Code Online (Sandbox Code Playgroud)\n

现在,flagsvariable 等于 None。

\n

我们可以使用&运算符检查该值是否存在:

\n
Console.WriteLine((flags & UserType.Driver) == UserType.Driver);\n
Run Code Online (Sandbox Code Playgroud)\n

结果是False

\n

另外,我们可以使用以下HasFlag方法来做到这一点:

\n
Console.WriteLine(flags.HasFlag(UserType.Driver));\n
Run Code Online (Sandbox Code Playgroud)\n

而且,结果将为 False。

\n

正如我们所看到的,使用&运算符和HasFlag方法的两种方式都会给出相同的结果,但是我们应该使用哪一种呢?为了找到答案,我们将在几个框架上测试性能。

\n

衡量绩效

\n

首先,我们将创建一个控制台应用程序,并在.csproj文件中将标签替换TargetFramwork为以下TargetFramworks标签:

\n
<TargetFrameworks>net48;netcoreapp3.1;net6.0</TargetFrameworks>\nWe use the TargetFramworks tag to support multiple frameworks: .NET Framework 4.8, .Net Core 3.1, and .Net 6.0.\n
Run Code Online (Sandbox Code Playgroud)\n

其次,让\xe2\x80\x99s引入该BenchmarkDotNet库来获取基准测试结果:

\n
[Benchmark]\npublic bool HasFlag()\n{\n    var result = false;\n    for (int i = 0; i < 100000; i++)\n    {\n        result = UserType.Employee.HasFlag(UserType.Driver);\n    }\n    return result;\n}\n[Benchmark]\npublic bool BitOperator()\n{\n    var result = false;\n    for (int i = 0; i < 100000; i++)\n    {\n        result = (UserType.Employee & UserType.Driver) == UserType.Driver;\n    }\n    return result;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们向类添加[SimpleJob(RuntimeMoniker.Net48)][SimpleJob(RuntimeMoniker.NetCoreApp31)]、 和[SimpleJob(RuntimeMoniker.Net60)]属性HasFlagBenchmarker来查看不同版本之间的性能差异.NET Framework / .NET Core

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
方法工作运行意思是错误标准差中位数
有标志.NET 6.0.NET 6.037.79 我们3.781 我们11.15 我们30.30 我们
位运算符.NET 6.0.NET 6.038.17 我们3.853 我们11.36 我们30.38 我们
有标志.NET核心3.1.NET核心3.138.31 我们3.939 我们11.61 我们30.37 我们
位运算符.NET核心3.1.NET核心3.138.07 我们3.819 我们11.26 我们30.33 我们
有标志.NET框架4.8.NET框架4.82,893.10 美元342.563 我们1,010.06 我们2,318.93 美元
位运算符.NET框架4.8.NET框架4.838.04 我们3.920 我们11.56 我们30.17 我们
\n
\n

所以, in .NET Framework 4.8aHasFlag方法比BitOperator. 但是,性能在.Net Core 3.1和中有所提高.Net 6.0。所以在新版本中,我们可以同时使用这两种方式。

\n


Jay*_*ney 10

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

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}
Run Code Online (Sandbox Code Playgroud)

  • 这是不正确的,即使枚举未标记为“Flags”,您也可以使用位掩码。 (7认同)