我不时会看到如下的枚举:
[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枚举常量之间执行逻辑而非按位比较,以确定是否设置了数值中的任何位.
你可以找到更多信息有关标志属性及其在使用MSDN和MSDN上的标志设计
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更容易,等等.它对您的代码没有影响,因为它们都是在编译时完成的
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)
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)
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)
我发现这比检查包含的标志的大多数方法更容易阅读.
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)
我想,这表示清楚的标志是如何工作在幕后.
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)
ruf*_*fin 13
关于if ((x & y) == y)...
构造,我有些过于冗长,特别是如果x
AND 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 定义一个表示用户类型的枚举:
\npublic 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例如,在一家送货公司,我们知道管理员和司机都是员工。那么让\xe2\x80\x99s添加一个新的枚举项Employee
。稍后,我们将向您展示如何用它代表管理员和驱动程序:
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 属性
\nFlags 是一个属性,它允许我们将枚举表示为值的集合 \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 的幂对值进行编号。如果没有两者,这将无法工作。
现在回到我们之前的问题,我们可以Employee
使用|
运算符来表示:
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
:
[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 两个值的组合。
对 Flags 属性的操作
\n我们可以使用按位运算符来处理Flags
.
初始化一个值
\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现在,我们可以定义一个变量:
\nvar flags = UserType.None;\n
Run Code Online (Sandbox Code Playgroud)\n添加一个值
\n我们可以通过使用|
运算符来增加值:
flags |= UserType.Driver;\n
Run Code Online (Sandbox Code Playgroud)\n现在,flags 变量等于 Driver。
\n删除一个值
\n我们可以通过使用运算符删除值&, ~
:
flags &= ~UserType.Driver;\n
Run Code Online (Sandbox Code Playgroud)\n现在,flagsvariable 等于 None。
\n我们可以使用&
运算符检查该值是否存在:
Console.WriteLine((flags & UserType.Driver) == UserType.Driver);\n
Run Code Online (Sandbox Code Playgroud)\n结果是False
。
另外,我们可以使用以下HasFlag
方法来做到这一点:
Console.WriteLine(flags.HasFlag(UserType.Driver));\n
Run Code Online (Sandbox Code Playgroud)\n而且,结果将为 False。
\n正如我们所看到的,使用&
运算符和HasFlag
方法的两种方式都会给出相同的结果,但是我们应该使用哪一种呢?为了找到答案,我们将在几个框架上测试性能。
衡量绩效
\n首先,我们将创建一个控制台应用程序,并在.csproj
文件中将标签替换TargetFramwork
为以下TargetFramworks
标签:
<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
库来获取基准测试结果:
[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
:
方法 | 工作 | 运行 | 意思是 | 错误 | 标准差 | 中位数 |
---|---|---|---|---|---|---|
有标志 | .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 我们 |
所以, in .NET Framework 4.8
aHasFlag
方法比BitOperator
. 但是,性能在.Net Core 3.1
和中有所提高.Net 6.0
。所以在新版本中,我们可以同时使用这两种方式。
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)