我不时会看到如下枚举:

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

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

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


当前回答

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

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

我认为这一表述清楚地表明了旗帜在幕后的运作方式。

其他回答

当可枚举值表示枚举成员。这里我们使用按位运算符|和&实例[标志]公共枚举边{Left=0,Right=1,Top=2,Bottom=3}Sides leftRight=侧面.Left|侧面.Right;Console.WriteLine(左-右)//左侧、右侧string stringValue=leftRight.ToString();Console.WriteLine(字符串值)//左侧、右侧侧面s=侧面。左侧;s |=侧面。右侧;Console.WriteLine//左侧、右侧s^=侧面。右侧;//切换侧面。右侧Console.WriteLine//左边

定义问题

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

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

我们定义了包含三个值的UserType枚举:Customer、Driver和Admin。

但如果我们需要代表一组价值观呢?

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

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

定义和声明Flags属性

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属性,我们必须回到数字的二进制表示。例如,我们可以将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。

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

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

结果为False。

此外,我们还可以使用HasFlag方法实现这一点:

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

此外,结果将为False。

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

衡量绩效

首先,我们将创建一个Console应用程序,在.csproj文件中,我们将用TargetFramworks标记替换TargetFramwworks标记:

<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类添加[SimpleJob(RuntimeMoniker.Net48)]、[SimpleJob(Runtime莫尼ker.NetCoreApp31)]和[SimpleJob(RuntimeMonker.Net60)]属性,以查看不同版本的.NET Framework/.NET Core之间的性能差异:

Method Job Runtime Mean Error StdDev Median
HasFlag .NET 6.0 .NET 6.0 37.79 us 3.781 us 11.15 us 30.30 us
BitOperator .NET 6.0 .NET 6.0 38.17 us 3.853 us 11.36 us 30.38 us
HasFlag .NET Core 3.1 .NET Core 3.1 38.31 us 3.939 us 11.61 us 30.37 us
BitOperator .NET Core 3.1 .NET Core 3.1 38.07 us 3.819 us 11.26 us 30.33 us
HasFlag .NET Framework 4.8 .NET Framework 4.8 2,893.10 us 342.563 us 1,010.06 us 2,318.93 us
BitOperator .NET Framework 4.8 .NET Framework 4.8 38.04 us 3.920 us 11.56 us 30.17 us

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

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

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

我认为这一表述清楚地表明了旗帜在幕后的运作方式。

@Nidonucu公司

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

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

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

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