简体   繁体   English

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

[英]What does the [Flags] Enum Attribute mean in C#?

From time to time I see an enum like the following:有时我会看到如下枚举:

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

I don't understand what exactly the [Flags] attribute does.我不明白[Flags]属性到底是做什么的。

Anyone have a good explanation or example they could post?任何人都可以发布一个很好的解释或示例?

The [Flags] attribute should be used whenever the enumerable represents a collection of possible values, rather than a single value.只要可枚举表示可能值的集合,而不是单个值,就应该使用[Flags]属性。 Such collections are often used with bitwise operators, for example:此类集合通常与按位运算符一起使用,例如:

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Note that the [Flags] attribute doesn't enable this by itself - all it does is allow a nice representation by the .ToString() method:请注意, [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"

It is also important to note that [Flags] does not automatically make the enum values powers of two.同样重要的是要注意[Flags]不会自动使枚举值成为 2 的幂。 If you omit the numeric values, the enum will not work as one might expect in bitwise operations, because by default the values start with 0 and increment.如果省略数值,则枚举将不会像按位运算预期的那样工作,因为默认情况下,这些值从 0 开始并递增。

Incorrect declaration:不正确的声明:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}

The values, if declared this way, will be Yellow = 0, Green = 1, Red = 2, Blue = 3. This will render it useless as flags.如果以这种方式声明,这些值将是 Yellow = 0、Green = 1、Red = 2、Blue = 3。这将使其无法用作标志。

Here's an example of a correct declaration:下面是一个正确声明的例子:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

To retrieve the distinct values in your property, one can do this:要检索属性中的不同值,可以执行以下操作:

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

or prior to .NET 4:或在 .NET 4 之前:

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

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

Under the covers在封面下

This works because you used powers of two in your enumeration.这是有效的,因为您在枚举中使用了 2 的幂。 Under the covers, your enumeration values look like this in binary ones and zeros:在幕后,您的枚举值在二进制 1 和 0 中如下所示:

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

Similarly, after you've set your property AllowedColors to Red, Green and Blue using the binary bitwise OR |同样,在使用二进制按位 OR |AllowedColors属性设置为 Red、Green 和 Blue 之后, operator, AllowedColors looks like this:运算符AllowedColors如下所示:

myProperties.AllowedColors: 00001110

So when you retrieve the value you are actually performing bitwise AND & on the values:因此,当您检索该值时,您实际上是对这些值执行按位 AND &操作:

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

The None = 0 value None = 0 值

And regarding the use of 0 in your enumeration, quoting from MSDN:关于在枚举中使用0 ,引用自 MSDN:

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

Use None as the name of the flag enumerated constant whose value is zero.使用 None 作为值为零的标志枚举常量的名称。 You cannot use the None enumerated constant in a bitwise AND operation to test for a flag because the result is always zero.您不能在按位 AND 运算中使用 None 枚举常量来测试标志,因为结果始终为零。 However, you can perform a logical, not a bitwise, comparison between the numeric value and the None enumerated constant to determine whether any bits in the numeric value are set.但是,您可以在数值和 None 枚举常量之间执行逻辑比较,而不是按位比较,以确定是否设置了数值中的任何位。

You can find more info about the flags attribute and its usage at msdn and designing flags at msdn您可以在msdn 上找到有关 flags 属性及其用法的更多信息,并在msdn 上设计标志

You can also do this你也可以这样做

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

I find the bit-shifting easier than typing 4,8,16,32 and so on.我发现位移比输入 4、8、16、32 等更容易。 It has no impact on your code because it's all done at compile time它对您的代码没有影响,因为它都是在编译时完成的

Combining answers https://stackoverflow.com/a/8462/1037948 (declaration via bit-shifting) and https://stackoverflow.com/a/9117/1037948 (using combinations in declaration) you can bit-shift previous values rather than using numbers.结合答案https://stackoverflow.com/a/8462/1037948 (通过位移位声明)和https://stackoverflow.com/a/9117/1037948 (在声明中使用组合)你可以位移以前的值而不是而不是使用数字。 Not necessarily recommending it, but just pointing out you can.不一定推荐它,但只是指出你可以。

Rather than:而不是:

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

You can declare你可以声明

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

Confirming with LinqPad:使用 LinqPad 确认:

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

Results in:结果是:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8

Please see the following for an example which shows the declaration and potential usage:请参阅以下示例,其中显示了声明和潜在用法:

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

In extension to the accepted answer, in C#7 the enum flags can be written using binary literals:作为已接受答案的扩展,在 C#7 中,可以使用二进制文字编写枚举标志:

[Flags]
public enum MyColors
{
    None   = 0b0000,
    Yellow = 0b0001,
    Green  = 0b0010,
    Red    = 0b0100,
    Blue   = 0b1000
}

I think this representation makes it clear how the flags work under the covers .我认为这种表示方式清楚地说明了这些标志是如何在幕后工作

I asked recently about something similar.最近问了类似的事情。

If you use flags you can add an extension method to enums to make checking the contained flags easier (see post for detail)如果您使用标志,您可以向枚举添加扩展方法,以便更轻松地检查包含的标志(详情请参阅帖子)

This allows you to do:这允许您执行以下操作:

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

Then you can do:然后你可以这样做:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

if( opt.IsSet( PossibleOptions.OptionOne ) ) {
    //optionOne is one of those set
}

I find this easier to read than the most ways of checking the included flags.我发现这比检查包含的标志的大多数方法更容易阅读。

When working with flags I often declare additional None and All items.使用标志时,我经常声明额外的 None 和 All 项目。 These are helpful to check whether all flags are set or no flag is set.这些有助于检查是否设置了所有标志或未设置标志。

[Flags] 
enum SuitsFlags { 

    None =     0,

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

    All =      ~(~0 << 4)

}

Usage:用法:

Spades | Clubs | Diamonds | Hearts == All  // true
Spades & Clubs == None  // true


Update 2019-10: 2019-10 更新:

Since C# 7.0 you can use binary literals, which are probably more intuitive to read:从 C# 7.0 开始,您可以使用二进制文字,这可能更直观:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

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

    All =      0b1111

}

@Nidonocu @尼多诺库

To add another flag to an existing set of values, use the OR assignment operator.要将另一个标志添加到现有值集,请使用 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;

There's something overly verbose to me about the if ((x & y) == y)... construct, especially if x AND y are both compound sets of flags and you only want to know if there's any overlap.关于if ((x & y) == y)...构造对我来说有些过于冗长,特别是如果x AND y都是复合标志集,而您只想知道是否有任何重叠。

In this case, all you really need to know is if there's a non-zero value[1] after you've bitmasked .在这种情况下,您真正​​需要知道的是在您对位掩码后是否有非零值 [1]

[1] See Jaime's comment. [1] 见 Jaime 的评论。 If we were authentically bitmasking , we'd only need to check that the result was positive.如果我们是真正的bitmasking ,我们只需要检查结果是否为正。 But since enum s can be negative, even, strangely, when combined with the [Flags] attribute , it's defensive to code for != 0 rather than > 0 .但是由于enum可能是负数,甚至,奇怪的是,当与[Flags]属性结合使用时,编码为!= 0而不是> 0是防御性的。

Building off of @andnil's setup...建立在@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 allow you to use bitmasking inside your enumeration.标志允许您在枚举中使用位掩码。 This allows you to combine enumeration values, while retaining which ones are specified.这允许您组合枚举值,同时保留指定的值。

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

Apologies if someone already noticed this scenario.如果有人已经注意到这种情况,我们深表歉意。 A perfect example of flags we can see in reflection.我们可以在反射中看到的标志的完美示例。 Yes Binding Flags ENUM .绑定标志 ENUM

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

Usage用法

// 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 [] {});

Define the Problem定义问题

Let's define an enum that represents the types of users:让我们定义一个代表用户类型的枚举:

public enum UserType
{
    Customer = 1,
    Driver = 2,  
    Admin = 3,
}

We define the UserType enum that contains three values: Customer, Driver, and Admin.我们定义了包含三个值的 UserType 枚举: Customer, Driver, and Admin.

But what if we need to represent a collection of values?但是,如果我们需要表示值的集合怎么办?

For example, at a delivery company, we know that both the Admin and the Driver are employees.例如,在一家快递公司,我们知道管理员和司机都是员工。 So let's add a new enumeration item Employee .那么让我们添加一个新的枚举项Employee Later on, we will show you how we can represent both the admin and the driver with it:稍后,我们将向您展示如何用它来表示管理员和驱动程序:

public enum UserType
{   
    Customer = 1,
    Driver = 2,  
    Admin = 3,
    Employee = 4
}

Define and Declare a Flags Attribute定义和声明标志属性

A Flags is an attribute that allows us to represent an enum as a collection of values rather than a single value. Flags 是一个属性,它允许我们将枚举表示为值的集合而不是单个值。 So, let's see how we can implement the Flags attribute on enumeration:那么,让我们看看如何在枚举上实现 Flags 属性:

[Flags]
public enum UserType
{   
    Customer = 1,
    Driver = 2,  
    Admin = 4,
}

We add the Flags attribute and number the values with powers of 2. Without both, this won't work.我们添加Flags属性并用 2 的幂对值进行编号。没有这两个,这将无法工作。

Now going back to our previous problem, we can represent Employee using the |现在回到我们之前的问题,我们可以使用|来表示Employee operator:操作员:

var employee = UserType.Driver | UserType.Admin;

Also, we can define it as a constant inside the enum to use it directly:此外,我们可以将其定义为枚举内部的常量以直接使用它:

[Flags]
public enum UserType
{                
    Customer = 1,             
    Driver = 2,               
    Admin = 4,                
    Employee = Driver | Admin
}

Behind the Scenes幕后花絮

To understand the Flags attribute better, we must go back to the binary representation of the number.为了更好地理解Flags属性,我们必须将 go 转换回数字的二进制表示形式。 For example, we can represent 1 as binary 0b_0001 and 2 as 0b_0010 :例如,我们可以将 1 表示为二进制0b_0001 ,将 2 表示为0b_0010

[Flags]
public enum UserType
{
    Customer = 0b_0001,
    Driver = 0b_0010,
    Admin = 0b_0100,
    Employee = Driver | Admin, //0b_0110
}

We can see that each value is represented in an active bit.我们可以看到每个值都用一个活动位表示。 And this is where the idea of numbering the values with the power of 2 came from.这就是用 2 的幂对值进行编号的想法的来源。 We can also note that Employee contains two active bits, that is, it is a composite of two values Driver and Admin.我们还可以注意到, Employee包含两个活动位,也就是说,它是两个值 Driver 和 Admin 的组合。

Operations on Flags Attribute标志属性操作

We can use the bitwise operators to work with Flags .我们可以使用按位运算符来处理Flags

Initialize a Value初始化一个值

For the initialization, we should use the value 0 named None, which means the collection is empty:对于初始化,我们应该使用名为 None 的值 0,这意味着集合为空:

[Flags]
public enum UserType
{
    None = 0,
    Customer = 1,
    Driver = 2,
    Admin = 4,
    Employee = Driver | Admin
}

Now, we can define a variable:现在,我们可以定义一个变量:

var flags = UserType.None;

Add a Value添加一个值

We can add value by using |我们可以通过使用|来增加价值operator:操作员:

flags |= UserType.Driver;

Now, the flags variable equals Driver.现在,flags 变量等于 Driver。

Remove a Value删除值

We can remove value by use &, ~ operators:我们可以通过使用&, ~运算符来删除值:

flags &= ~UserType.Driver;

Now, flagsvariable equals None.现在,flagsvariable 等于 None。

We can check if the value exists by using & operator:我们可以使用&运算符检查该值是否存在:

Console.WriteLine((flags & UserType.Driver) == UserType.Driver);

The result is False .结果是False

Also, we can do this by using the HasFlag method:另外,我们可以使用HasFlag方法来做到这一点:

Console.WriteLine(flags.HasFlag(UserType.Driver));

Also, the result will be False.此外,结果将为 False。

As we can see, both ways, using the & operator and the HasFlag method, give the same result, but which one should we use?正如我们所见,使用&运算符和HasFlag方法的两种方法都给出了相同的结果,但是我们应该使用哪一种呢? To find out, we will test the performance on several frameworks.为了找出答案,我们将测试几个框架的性能。

Measure the Performance衡量绩效

First, we will create a Console App, and in the .csproj file we will replace the TargetFramwork tag with the TargetFramworks tag:首先,我们将创建一个控制台应用程序,并在.csproj文件中将TargetFramwork标签替换为TargetFramworks标签:

<TargetFrameworks>net48;netcoreapp3.1;net6.0</TargetFrameworks>
We use the TargetFramworks tag to support multiple frameworks: .NET Framework 4.8, .Net Core 3.1, and .Net 6.0.

Secondly, let's introduce the BenchmarkDotNet library to get the benchmark results:其次,让我们引入BenchmarkDotNet库来获取基准测试结果:

[Benchmark]
public bool HasFlag()
{
    var result = false;
    for (int i = 0; i < 100000; i++)
    {
        result = UserType.Employee.HasFlag(UserType.Driver);
    }
    return result;
}
[Benchmark]
public bool BitOperator()
{
    var result = false;
    for (int i = 0; i < 100000; i++)
    {
        result = (UserType.Employee & UserType.Driver) == UserType.Driver;
    }
    return result;
}

We add [SimpleJob(RuntimeMoniker.Net48)] , [SimpleJob(RuntimeMoniker.NetCoreApp31)] , and [SimpleJob(RuntimeMoniker.Net60)] attributes to the HasFlagBenchmarker class to see the performance differences between different versions of .NET Framework / .NET Core :我们在 HasFlagBenchmarker class 中添加[SimpleJob(RuntimeMoniker.Net48)][SimpleJob(RuntimeMoniker.NetCoreApp31)][SimpleJob(RuntimeMoniker.Net60)]属性,看看不同版本的HasFlagBenchmarker .NET Framework / .NET Core :30581526 的性能差异

Method方法 Job工作 Runtime运行 Mean意思 Error错误 StdDev标准偏差 Median中位数
HasFlag有标志 .NET 6.0 .NET 6.0 .NET 6.0 .NET 6.0 37.79 us 37.79 我们 3.781 us 3.781 我们 11.15 us 11.15 我们 30.30 us 30.30 我们
BitOperator比特运算符 .NET 6.0 .NET 6.0 .NET 6.0 .NET 6.0 38.17 us 38.17 我们 3.853 us 3.853 我们 11.36 us 11.36 我们 30.38 us 30.38 我们
HasFlag有标志 .NET Core 3.1 .NET 核心3.1 .NET Core 3.1 .NET 核心3.1 38.31 us 38.31 我们 3.939 us 3.939 我们 11.61 us 11.61 我们 30.37 us 30.37 我们
BitOperator比特运算符 .NET Core 3.1 .NET 核心3.1 .NET Core 3.1 .NET 核心3.1 38.07 us 38.07 我们 3.819 us 3.819 我们 11.26 us 11.26 我们 30.33 us 30.33 我们
HasFlag有标志 .NET Framework 4.8 .NET 框架 4.8 .NET Framework 4.8 .NET 框架 4.8 2,893.10 us 2,893.10 美元 342.563 us 342.563 我们 1,010.06 us 1,010.06 美元 2,318.93 us 2,318.93 美元
BitOperator比特运算符 .NET Framework 4.8 .NET 框架 4.8 .NET Framework 4.8 .NET 框架 4.8 38.04 us 38.04 我们 3.920 us 3.920 我们 11.56 us 11.56 我们 30.17 us 30.17 我们

So, in .NET Framework 4.8 a HasFlag method was much slower than the BitOperator .因此,在.NET Framework 4.8中, HasFlag方法比BitOperator慢得多。 But, the performance improves in .Net Core 3.1 and .Net 6.0 .但是,性能在.Net Core 3.1.Net 6.0中有所提高。 So in newer versions, we can use both ways.所以在较新的版本中,我们可以同时使用这两种方式。

  • Flags are used when an enumerable value represents a collection of enum members.当可枚举值表示枚举成员的集合时使用标志。

  • here we use bitwise operators, |这里我们使用按位运算符,| and &和 &

  • Example例子

     [Flags] public enum Sides { Left=0, Right=1, Top=2, Bottom=3 } Sides leftRight = Sides.Left | Sides.Right; Console.WriteLine (leftRight);//Left, Right string stringValue = leftRight.ToString(); Console.WriteLine (stringValue);//Left, Right Sides s = Sides.Left; s |= Sides.Right; Console.WriteLine (s);//Left, Right s ^= Sides.Right; // Toggles Sides.Right Console.WriteLine (s); //Left

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM