แอตทริบิวต์ [Flags] Enum หมายถึงอะไรใน C #


1445

บางครั้งฉันเห็น enum เช่นนี้:

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

ฉันไม่เข้าใจว่า[Flags]แอตทริบิวต์ทำอะไร

ใครมีคำอธิบายหรือตัวอย่างที่ดีที่พวกเขาสามารถโพสต์?


นอกจากนี้ยังเป็นที่น่าสังเกตนอกเหนือจากคำตอบที่ยอมรับแล้วว่า VB.NET ต้องการ [ธง] - อย่างน้อยตามผู้ใช้. NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/
Rushyo

8
หมายเหตุไม่จำเป็นต้องใช้ใน VB วันนี้ บันทึกพฤติกรรมเป็น C # - เพียงแค่เปลี่ยนผลลัพธ์ ToString () หมายเหตุคุณยังสามารถทำตรรกะหรือภายใน Enum เอง เด็ดมาก Cat = 1, Dog = 2, CatAndDog = Cat || หมา.
Chalky

14
@ Chalky คุณหมายถึงCatAndDog = Cat | Dog(ตรรกะหรือแทนเงื่อนไข) ฉันถือว่า?
DdW

5
@DdW ถูกต้องบางส่วน: | ควรใช้ แต่ | เรียกว่าไบนารีหรือ II เป็นตรรกะหรือ (ที่อนุญาตการลัดวงจร): อย่างน้อยตาม Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

คำตอบ:


2152

[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] ว่าไม่ได้ทำให้พลังค่า enum ของทั้งสองโดยอัตโนมัติ หากคุณเว้นค่าตัวเลข enum จะไม่ทำงานอย่างที่คาดไว้ในการดำเนินการระดับบิตเนื่องจากโดยค่าเริ่มต้นค่าเริ่มต้นด้วย 0 และเพิ่มขึ้น

ประกาศไม่ถูกต้อง:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}

ค่าถ้าประกาศด้วยวิธีนี้จะเป็นสีเหลือง = 0, สีเขียว = 1, สีแดง = 2, สีน้ำเงิน = 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...
}    

ภายใต้ฝาครอบ

สิ่งนี้ได้ผลเพราะคุณใช้พลังสองอย่างในการแจงนับ ภายใต้หน้าปกค่าการแจงนับของคุณจะมีลักษณะเช่นนี้ในไบนารีและศูนย์:

 Yellow: 00000001
 Green:  00000010
 Red:    00000100
 Blue:   00001000

ในทำนองเดียวกันหลังจากที่คุณตั้งค่าคุณสมบัติAllowedColorsของคุณเป็นสีแดงสีเขียวและสีน้ำเงินโดยใช้ตัวดำเนินการ binary bitwise หรือ|ตัวดำเนินการAllowedColorsจะมีลักษณะดังนี้:

myProperties.AllowedColors: 00001110

ดังนั้นเมื่อคุณดึงค่าที่คุณกำลังดำเนินการจริง bitwise และ&ค่า:

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 เป็นชื่อของค่าคงที่ที่ระบุค่าสถานะซึ่งเป็นศูนย์ คุณไม่สามารถใช้ค่าคงที่ไม่มีการแจกแจงในการดำเนินการระดับบิตและระดับบิตเพื่อทดสอบการตั้งค่าสถานะเนื่องจากผลลัพธ์จะเป็นศูนย์เสมอ อย่างไรก็ตามคุณสามารถดำเนินการทางตรรกะไม่ใช่ค่าบิตเปรียบเทียบระหว่างค่าตัวเลขและค่าคงที่ไม่มีการแจกแจงเพื่อกำหนดว่าบิตใด ๆ ในค่าตัวเลขถูกตั้งค่า

คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับแอ็ตทริบิวต์ค่าสถานะและการใช้งานได้ที่msdnและการออกแบบค่าสถานะที่ msdn


156
ธงตัวเองไม่ทำอะไรเลย นอกจากนี้ C # ไม่จำเป็นต้องมีค่าสถานะต่อ se แต่ToStringการดำเนินงานของ enum ของคุณใช้ธงและเพื่อไม่Enum.IsDefined, Enum.Parseฯลฯ พยายามที่จะเอาธงและดูที่ผลของ MyColor.Yellow บน | MyColor.Red; ไม่ว่าคุณจะได้รับ "5" กับธงคุณได้รับ "สีเหลือง, สีแดง" ส่วนอื่นของเฟรมเวิร์กก็ใช้ [Flags] (เช่น XML Serialization)
Ruben

7
// Yellow ถูกตั้งค่าแล้ว ... ฉันพบว่ามันทำให้เข้าใจผิดเล็กน้อย ไม่มีการตั้งค่าใดเลยนั่นหมายความว่าสีเหลืองเป็นสมาชิกของ AllowedColors ของคุณบางทีอาจจะดีกว่าที่จะอนุญาต // สีเหลือง?
Scott Weaver

48
ฉันชอบที่จะใช้ค่าคงที่ของรูปแบบ A = 1 << 0, B = 1 << 1, C = 1 << 2 ... ง่ายต่อการอ่านเข้าใจตรวจสอบและเปลี่ยนแปลงได้ง่ายขึ้น
Nick Westgate

2
@ บอร์เดน, ใช่แล้ว! ฉันพบสิ่งนี้: msdn.microsoft.com/en-us/library/system.enum.aspx - ดูส่วน "ข้อสังเกต": "Enum เป็นคลาสพื้นฐานสำหรับการแจกแจงทั้งหมดใน. NET Framework" และ "การแจงนับไม่ได้รับการสืบทอดจาก Enum อย่างชัดเจนความสัมพันธ์ในการสืบทอดจะได้รับการจัดการโดยคอมไพเลอร์" ดังนั้นเมื่อคุณเขียน: public enum bla bla bla - นี่คือประเภทของค่า แต่วิธีการ HasFlag ต้องการให้ท่านที่จะให้เขาอินสแตนซ์ของ System.Enum ซึ่งเป็นระดับ (ชนิดอ้างอิง :)
อเล็ก Chepovoi

2
หากคุณต้องการแยกแฟล็กออกจากการแจงนับให้ใช้ xor ซึ่งคือ ^ ใน C # myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;ดังนั้นถ้าคุณมี คุณสามารถทำได้:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe

779

คุณสามารถทำได้

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

ฉันพบว่าการขยับบิตทำได้ง่ายกว่าการพิมพ์ 4,8,16,32 เป็นต้น มันไม่มีผลกระทบกับรหัสของคุณเพราะมันทำในเวลารวบรวม


18
นั่นคือสิ่งที่ฉันหมายถึงฉันชอบที่จะมี int เต็มในซอร์สโค้ด ถ้าฉันมีคอลัมน์ในตารางในฐานข้อมูลชื่อ MyEnum ที่เก็บค่าหนึ่งใน enums และบันทึกมี 131,072 ฉันจะต้องออกเครื่องคิดเลขของฉันเพื่อหาว่าสอดคล้องกับค่า enum 1 << 17 ตรงข้ามกับการเห็นค่า 131,072 ที่เขียนในแหล่งที่มา
JeremyWeir

6
@ jwg ฉันเห็นด้วยว่ามันเป็นเรื่องงี่เง่าที่จะต้องกังวลเกี่ยวกับประสิทธิภาพรันไทม์ของสิ่งนี้ แต่เหมือนกันทั้งหมดฉันคิดว่ามันดีที่รู้ว่านี่จะไม่เป็นการแทรกบิตกะที่ใดก็ตามที่คุณใช้ enum สิ่ง 'ที่เรียบร้อย' มากกว่าสิ่งใดที่เกี่ยวข้องกับการแสดง
Orion Edwards

18
@JeremyWeir - บิตจำนวนมากจะถูกตั้งค่าการแจงค่าสถานะ ดังนั้นวิธีการวิเคราะห์ข้อมูลของคุณจึงเป็นสิ่งที่ไม่เหมาะสม รันโพรซีเดอร์เพื่อแทนค่าจำนวนเต็มของคุณในรูปแบบไบนารี 131,072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. ด้วยชุดบิตที่ 17 ค่า enum ที่กำหนด 1 << 17 สามารถกำหนดได้อย่างง่ายดาย 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. ค่า enum กำหนด 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. ฯลฯ ฯลฯ สามารถกำหนดได้โดยไม่ต้องแม้แต่ ความช่วยเหลือของเครื่องคิดเลขเลย .. ซึ่งฉันสงสัยว่าคุณจะสามารถตัดสินใจได้โดยไม่ต้องรับตัวแทนไบนารี
Brett Caswell

13
นอกจากนี้ยังชี้ให้เห็นว่าคุณสามารถเปลี่ยนจากค่า enum "ก่อนหน้า" แทนที่จะเป็นจากตัวเลขโดยตรงเช่นThird = Second << 1แทนที่จะเป็นThird = 1 << 2- ดูคำอธิบายที่สมบูรณ์ยิ่งขึ้นด้านล่าง
drzaus

26
ฉันเช่นนี้ แต่เมื่อคุณได้รับการขีด จำกัด บนของชนิด enum ต้นแบบคอมไพเลอร์ไม่เตือนต่อกะบิตเช่น1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2และอื่น ๆ ในทางตรงกันข้ามถ้าคุณบอกว่าThirtySecond = 2147483648เป็น int type enum คอมไพเลอร์จะพ่นข้อผิดพลาด
aaaantoine

115

การรวมคำตอบ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

23
ชุดค่าผสมนี้เป็นคำแนะนำที่ดี แต่ฉันคิดว่าการเปลี่ยนแปลงบิตที่ถูกโยงจะมีแนวโน้มที่จะเกิดข้อผิดพลาดในการคัดลอกและวางเช่นสอง = หนึ่ง << 1, สาม = หนึ่ง << 1, ฯลฯ ... การเพิ่มจำนวนเต็มของ ฟอร์ม 1 << n ปลอดภัยยิ่งขึ้นและเจตนาชัดเจนยิ่งขึ้น
Rupert Rawnsley

2
@RupertRawnsley เพื่ออ้างคำตอบของฉัน:> ไม่จำเป็นต้องแนะนำ แต่เพียงชี้ให้เห็นว่าคุณสามารถ
drzaus

49

โปรดดูตัวอย่างต่อไปนี้ซึ่งแสดงการประกาศและการใช้งานที่เป็นไปได้:

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

ตัวอย่างนี้ใช้ได้แม้ว่าคุณจะออกจาก [ธง] มันเป็นส่วนที่ [Flags] ที่ฉันพยายามเรียนรู้
gbarry

39

ในส่วนขยายของคำตอบที่ยอมรับใน C # 7 ธง enum สามารถเขียนได้โดยใช้ตัวอักษรไบนารี:

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

ผมคิดว่าการแสดงนี้จะทำให้มันชัดเจนว่าธงทำงานภายใต้ครอบคลุม


2
และใน C # 7.2 มันชัดเจนยิ่งขึ้นด้วยตัวคั่นนำ ! 0b_0100
Vincenzo Petronio

37

ฉันถามเมื่อเร็ว ๆ นี้เกี่ยวกับสิ่งที่คล้ายกัน

หากคุณใช้การตั้งค่าสถานะคุณสามารถเพิ่มวิธีการขยายการ enums เพื่อให้ตรวจสอบการตั้งค่าสถานะที่มีอยู่ได้ง่ายขึ้น (ดูโพสต์เพื่อดูรายละเอียด)

วิธีนี้ช่วยให้คุณ:

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

ฉันพบว่าการอ่านนี้ง่ายกว่าวิธีตรวจสอบสถานะรวม


IsSet เป็นวิธีส่วนขยายที่ฉันคิด?
โรเบิร์ตคลีน

ใช่ - อ่านคำถามอื่นที่ฉันเชื่อมโยงเพื่อดูรายละเอียด: stackoverflow.com/questions/7244
Keith

71
.NET 4 เพิ่มHasFlagวิธีในการแจกแจงดังนั้นคุณสามารถทำได้opt.HasFlag( PossibleOptions.OptionOne )โดยไม่ต้องเขียนส่วนขยายของคุณเอง
Orion Edwards

1
ทราบว่าHasFlagเป็นได้ช้ากว่าการทำดำเนินงานค่าที่เหมาะสม
ไหวฮาลี

1
@WaiHaLee คุณสามารถใช้วิธีการขยาย CodeJam.EnumHelper.IsFlagSet ซึ่งถูกคอมไพล์เป็นเวอร์ชันที่รวดเร็วโดยใช้ Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_

22

@Nidonocu

ในการเพิ่มการตั้งค่าสถานะอื่นให้กับชุดของค่าที่มีอยู่ให้ใช้ตัวดำเนินการกำหนดค่า OR

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

18

เมื่อทำงานกับการตั้งค่าสถานะฉันมักจะประกาศเพิ่มเติมไม่มีรายการและทั้งหมด สิ่งเหล่านี้มีประโยชน์ในการตรวจสอบว่ามีการตั้งค่าสถานะทั้งหมดหรือไม่ตั้งค่าสถานะ

[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

}


14

มีบางอย่างที่เป็นสุดเหวี่ยง verbose กับผมเกี่ยวกับการif ((x & y) == y)...สร้างโดยเฉพาะอย่างยิ่งถ้าxและyมีทั้งชุดสารประกอบของธงและคุณเพียงต้องการที่จะรู้ว่าถ้ามีใด ๆที่ทับซ้อนกัน

ในกรณีนี้สิ่งที่คุณต้องการจริงๆที่จะรู้คือถ้ามีค่าที่ไม่ใช่ศูนย์ [1] หลังจากที่คุณได้ bitmasked

[1] ดูความคิดเห็นของ Jaime หากเราทำการbitmasking อย่างแท้จริงเราจะต้องตรวจสอบว่าผลลัพธ์นั้นเป็นค่าบวกเท่านั้น แต่เนื่องจากenums สามารถเป็นค่าลบแม้แปลกเมื่อรวมกับ แอตทริบิวต์ก็ป้องกันสำหรับมากกว่า[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();
        }
    }
}

Enum อาจขึ้นอยู่กับประเภทที่ลงชื่อดังนั้นคุณควรใช้ "! = 0" แทนที่จะเป็น "> 0"
กา

@ JaimePardos - ตราบใดที่เรารักษาจำนวนไบต์ที่ซื่อสัตย์อย่างที่ฉันทำในตัวอย่างนี้ไม่มีแนวคิดเรื่องลบ เพียง 0 ถึง 255 ตามที่MSDN เตือน "ใช้ความระมัดระวังหากคุณกำหนดจำนวนลบเป็นค่าคงที่ที่ระบุเนื่องจากค่าสถานะ ... [นั้น] อาจทำให้โค้ดของคุณสับสนและกระตุ้นให้เกิดข้อผิดพลาดในการเขียนโค้ด" มันแปลกที่คิดในแง่ของ "ลบ bitflags"! ; ^) ฉันจะแก้ไขเพิ่มเติมอีกเล็กน้อย แต่คุณขวาถ้าเราทำค่าลบการใช้งานของเราเราจะต้องตรวจสอบenum != 0
ruffin

10

ค่าสถานะอนุญาตให้คุณใช้ bitmasking ในการแจงนับของคุณ สิ่งนี้ช่วยให้คุณสามารถรวมค่าการแจงนับในขณะที่การรักษาที่ระบุไว้

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}

0

ขออภัยหากบางคนสังเกตเห็นสถานการณ์นี้แล้ว ตัวอย่างที่สมบูรณ์แบบของธงที่เราสามารถมองเห็นได้ ใช่การเชื่อมต่อธง ENUM https://docs.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=netframework-4.8

[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 [] {});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.