[英]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 枚举常量之间执行逻辑比较,而不是按位比较,以确定是否设置了数值中的任何位。
你也可以这样做
[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.