簡體   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