繁体   English   中英

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

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

有时我会看到如下枚举:

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

我不明白[Flags]属性到底是做什么的。

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

只要可枚举表示可能值的集合,而不是单个值,就应该使用[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

同样,在使用二进制按位 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 上找到有关 flags 属性及其用法的更多信息,并在msdn 上设计标志

你也可以这样做

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

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

结合答案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

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

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

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

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

我认为这种表示方式清楚地说明了这些标志是如何在幕后工作

最近问了类似的事情。

如果您使用标志,您可以向枚举添加扩展方法,以便更轻松地检查包含的标志(详情请参阅帖子)

这允许您执行以下操作:

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

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

使用标志时,我经常声明额外的 None 和 All 项目。 这些有助于检查是否设置了所有标志或未设置标志。

[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

}

@尼多诺库

要将另一个标志添加到现有值集,请使用 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 的评论。 如果我们是真正的bitmasking ,我们只需要检查结果是否为正。 但是由于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]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}

如果有人已经注意到这种情况,我们深表歉意。 我们可以在反射中看到的标志的完美示例。 绑定标志 ENUM

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

定义问题

让我们定义一个代表用户类型的枚举:

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

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

但是,如果我们需要表示值的集合怎么办?

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

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

定义和声明标志属性

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

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

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

现在回到我们之前的问题,我们可以使用|来表示Employee 操作员:

var employee = UserType.Driver | UserType.Admin;

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

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

幕后花絮

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

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

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

标志属性操作

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

初始化一个值

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

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

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

var flags = UserType.None;

添加一个值

我们可以通过使用|来增加价值操作员:

flags |= UserType.Driver;

现在,flags 变量等于 Driver。

删除值

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

flags &= ~UserType.Driver;

现在,flagsvariable 等于 None。

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

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

结果是False

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

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

此外,结果将为 False。

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

衡量绩效

首先,我们将创建一个控制台应用程序,并在.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.

其次,让我们引入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;
}

我们在 HasFlagBenchmarker class 中添加[SimpleJob(RuntimeMoniker.Net48)][SimpleJob(RuntimeMoniker.NetCoreApp31)][SimpleJob(RuntimeMoniker.Net60)]属性,看看不同版本的HasFlagBenchmarker .NET Framework / .NET Core :30581526 的性能差异

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

因此,在.NET Framework 4.8中, HasFlag方法比BitOperator慢得多。 但是,性能在.Net Core 3.1.Net 6.0中有所提高。 所以在较新的版本中,我们可以同时使用这两种方式。

  • 当可枚举值表示枚举成员的集合时使用标志。

  • 这里我们使用按位运算符,| 和 &

  • 例子

     [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