Apa yang dimaksud dengan Atribut [Bendera] Enum dalam C #?

1447

Dari waktu ke waktu saya melihat enum seperti berikut:

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

Saya tidak mengerti apa sebenarnya [Flags]atribut tersebut.

Adakah yang punya penjelasan atau contoh bagus yang bisa mereka posting?

Brian Leahy
sumber
Juga patut dicatat, selain jawaban yang diterima, bahwa VB.NET sebenarnya membutuhkan [Bendera] - setidaknya menurut .NET guys: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/…
Rushyo
8
Catatan, tidak diperlukan dalam VB hari ini. Simpan perilaku sebagai C # - hanya mengubah output ToString (). Catatan, Anda juga dapat melakukan logika ATAU, DALAM Enum itu sendiri. Sangat keren. Cat = 1, Dog = 2, CatAndDog = Cat || Anjing.
Chalky
14
@ Chalky Maksudmu CatAndDog = Cat | Dog(logika ATAU bukannya Bersyarat), saya kira?
DdW
5
@DW, sebagian benar: | harus digunakan, tetapi | disebut biner OR. II adalah logika ATAU (yang memungkinkan hubungan arus pendek): Setidaknya menurut Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

Jawaban:

2154

The [Flags]atribut harus digunakan setiap kali enumerable merupakan kumpulan nilai yang mungkin, bukan nilai tunggal. Koleksi semacam itu sering digunakan dengan operator bitwise, misalnya:

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Perhatikan bahwa [Flags]atribut tidak mengaktifkan ini dengan sendirinya - semua yang dilakukannya adalah memperbolehkan representasi yang bagus dengan .ToString()metode:

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"

Penting juga untuk dicatat bahwa [Flags] tidak secara otomatis membuat enum nilai kekuatan dua. Jika Anda menghilangkan nilai numerik, enum tidak akan berfungsi seperti yang diharapkan dalam operasi bitwise, karena secara default nilai dimulai dengan 0 dan kenaikan.

Deklarasi salah:

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

Nilai-nilai, jika dinyatakan dengan cara ini, akan menjadi Kuning = 0, Hijau = 1, Merah = 2, Biru = 3. Ini akan menjadikannya tidak berguna sebagai bendera.

Berikut ini contoh deklarasi yang benar:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

Untuk mengambil nilai berbeda di properti Anda, orang dapat melakukan ini:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow is allowed...
}

atau sebelum .NET 4:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow is allowed...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green is allowed...
}    

Di bawah selimut

Ini berfungsi karena Anda menggunakan kekuatan dua dalam enumerasi Anda. Di bawah sampul, nilai enumerasi Anda terlihat seperti ini dalam nilai biner dan nol:

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

Demikian pula, setelah Anda mengatur properti Anda AllowColors ke Merah, Hijau dan Biru menggunakan bitwise biner ATAU |operator, DiizinkanColors terlihat seperti ini:

myProperties.AllowedColors: 00001110

Jadi ketika Anda mengambil nilai Anda sebenarnya melakukan bitwise DAN &pada nilai-nilai:

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Hey, this is the same as MyColor.Green!

Nilai None = 0

Dan mengenai penggunaan 0dalam enumerasi Anda, mengutip dari MSDN:

[Flags]
public enum MyColors
{
    None = 0,
    ....
}

Gunakan Tidak Ada sebagai nama bendera yang disebutkan konstanta yang nilainya nol. Anda tidak dapat menggunakan konstanta None yang disebutkan dalam operasi AND AND bitwise untuk menguji bendera karena hasilnya selalu nol. Namun, Anda dapat melakukan perbandingan logis, bukan bitwise, antara nilai numerik dan konstanta None yang disebutkan untuk menentukan apakah ada bit dalam nilai numerik yang ditetapkan.

Anda dapat menemukan lebih banyak info tentang atribut flags dan penggunaannya di msdn dan mendesain flags di msdn

andnil
sumber
156
Bendera itu sendiri tidak melakukan apa pun. Juga, C # tidak memerlukan Flags per se. Namun ToStringpelaksanaan enum Anda menggunakan Flags, dan begitu juga Enum.IsDefined, Enum.Parse, dll Cobalah untuk menghapus Flags dan melihat hasil MyColor.Yellow | MyColor.Red; tanpa itu Anda mendapatkan "5", dengan Bendera Anda mendapatkan "Kuning, Merah". Beberapa bagian lain dari kerangka kerja ini juga menggunakan [Flags] (misalnya, Serialisasi XML).
Ruben
7
// Kuning sudah diatur ..., saya menemukan sedikit menyesatkan. Tidak ada yang ditetapkan, itu berarti bahwa Yellow adalah anggota diizinkanColors Anda, mungkin lebih baik // Kuning diizinkan?
Scott Weaver
48
Saya lebih suka menggunakan konstanta dari bentuk A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Jauh lebih mudah untuk membaca, memahami, memeriksa dan mengubah secara visual.
Nick Westgate
2
@borrrden, sial yeahh! Saya menemukan ini: msdn.microsoft.com/en-us/library/system.enum.aspx - lihat bagian "Keterangan": "Enum adalah kelas dasar untuk semua enumerasi dalam .NET Framework." dan "Pencacahan tidak secara eksplisit mewarisi dari Enum; hubungan warisan ditangani secara implisit oleh kompiler." Jadi, ketika Anda menulis: public enum bla bla bla - ini adalah tipe nilai. Tapi, metode HasFlag ingin Anda memberinya contoh System.Enum yang merupakan kelas (tipe referensi :)
Aleksei Chepovoi
2
Jika Anda ingin mengecualikan bendera dari enumerasi, gunakan xor, yaitu ^ dalam C #. Jadi kalau sudah myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Anda dapat melakukan:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe
780

Anda juga bisa melakukan ini

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

Saya menemukan bit-shifting lebih mudah daripada mengetik 4,8,16,32 dan seterusnya. Itu tidak berdampak pada kode Anda karena semuanya dilakukan pada waktu kompilasi

Orion Edwards
sumber
18
Itulah yang saya maksud, saya lebih suka memiliki int penuh dalam kode sumber. Jika saya memiliki kolom di atas meja di basis data yang disebut MyEnum yang menyimpan nilai salah satu enum, dan catatan memiliki 131.072, saya akan perlu mengeluarkan kalkulator saya untuk mencari tahu yang sesuai dengan enum dengan nilai 1 << 17. Berbeda dengan hanya melihat nilai 131.072 tertulis dalam sumbernya.
JeremyWeir
6
@ jwg Saya setuju, itu konyol terlalu khawatir tentang kinerja runtime ini, tetapi semua sama, saya pikir itu baik untuk mengetahui bahwa ini tidak akan memasukkan bitshift di mana pun Anda menggunakan enum. Lebih dari hal yang 'rapi' daripada apa pun yang terkait dengan kinerja
Orion Edwards
18
@ JeremyWeir - Beberapa bit akan ditetapkan dalam nilai enumerasi bendera. Jadi metode analisis data Anda adalah yang tidak patut. Jalankan prosedur untuk mewakili nilai integer Anda dalam biner. 131.072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Dengan bit ke-17 yang ditetapkan, nilai enum yang diberikan 1 << 17 mudah ditentukan. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. nilai enum menetapkan 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. dll. Dapat ditentukan tanpa bantuan kalkulator sama sekali .. yang saya ragu Anda akan dapat menentukan sama sekali tanpa mendapatkan perwakilan biner.
Brett Caswell
13
Juga menunjukkan Anda dapat menggigit-bergeser dari nilai enum "sebelumnya" daripada langsung dari angka, yaitu Third = Second << 1daripada Third = 1 << 2- lihat deskripsi lebih lengkap di bawah ini
drzaus
26
Aku seperti ini, tetapi sekali Anda sampai ke batas atas dari jenis enum yang mendasari, compiler tidak memperingatkan terhadap bit pergeseran seperti 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2, dan sebagainya. Sebaliknya, jika Anda mengatakan ThirtySecond = 2147483648untuk tipe int enum, kompiler melempar kesalahan.
aaaantoine
115

Menggabungkan jawaban https://stackoverflow.com/a/8462/1037948 (deklarasi melalui bit-shifting) dan https://stackoverflow.com/a/9117/1037948 (menggunakan kombinasi dalam deklarasi) Anda dapat menggeser-geser nilai sebelumnya sebagai gantinya daripada menggunakan angka. Tidak perlu merekomendasikannya, tetapi hanya menunjukkan Anda bisa.

Daripada:

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

Anda bisa mendeklarasikan

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

Mengkonfirmasi dengan LinqPad:

foreach(var e in Enum.GetValues(typeof(Options))) {
    string.Format("{0} = {1}", e.ToString(), (byte)e).Dump();
}

Hasil dalam:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
drzaus
sumber
23
Kombinasi adalah rekomendasi yang baik, tapi saya pikir bit-shift dirantai akan rentan terhadap kesalahan salin dan tempel seperti Dua = Satu << 1, Tiga = Satu << 1, dll ... Bilangan bulat yang meningkat dari form 1 << n lebih aman dan tujuannya lebih jelas.
Rupert Rawnsley
2
@RupertRawnsley mengutip jawaban saya:> Tidak selalu merekomendasikannya, tetapi hanya menunjukkan Anda bisa
drzaus
49

Silakan lihat berikut ini untuk contoh yang menunjukkan deklarasi dan potensi penggunaan:

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");
            }
        }
    }
}
OJ.
sumber
Contoh ini berfungsi bahkan jika Anda meninggalkan [Bendera]. Ini bagian [Bendera] yang saya coba pelajari.
Gbarry
39

Sebagai perpanjangan dari jawaban yang diterima, di C # 7 bendera enum dapat ditulis menggunakan literal biner:

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

Saya pikir representasi ini memperjelas bagaimana bendera bekerja di bawah selimut .

Thorkil Holm-Jacobsen
sumber
2
Dan di C # 7.2 bahkan lebih jelas dengan pemisah terkemuka ! 0b_0100
Vincenzo Petronio
37

Saya bertanya baru - baru ini tentang sesuatu yang serupa.

Jika Anda menggunakan flag, Anda dapat menambahkan metode ekstensi ke enum untuk mempermudah memeriksa flag yang ada (lihat posting untuk detail)

Ini memungkinkan Anda untuk melakukan:

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

Maka Anda dapat melakukan:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

if( opt.IsSet( PossibleOptions.OptionOne ) ) {
    //optionOne is one of those set
}

Saya menemukan ini lebih mudah dibaca daripada kebanyakan cara memeriksa bendera yang disertakan.

Keith
sumber
IsSet adalah metode ekstensi yang saya asumsikan?
Robert MacLean
Ya - baca pertanyaan lain yang saya tautkan untuk perincian: stackoverflow.com/questions/7244
Keith
71
.NET 4 menambahkan HasFlagmetode untuk enumerasi, sehingga Anda dapat melakukannya opt.HasFlag( PossibleOptions.OptionOne )tanpa harus menulis ekstensi Anda sendiri
Orion Edwards
1
Perhatikan bahwa HasFlagini jauh lebih lambat daripada melakukan operasi bitwise.
Wai Ha Lee
1
@WaiHaLee Anda dapat menggunakan metode ekstensi CodeJam.EnumHelper.IsFlagSet yang dikompilasi ke versi cepat menggunakan Ekspresi: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_
22

@Nidonocu

Untuk menambahkan flag lain ke set nilai yang ada, gunakan operator penugasan ATAU.

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

Ketika bekerja dengan bendera, saya sering menyatakan Tidak Ada dan Semua item tambahan. Ini sangat membantu untuk memeriksa apakah semua flag diatur atau tidak ada flag yang ditetapkan.

[Flags] 
enum SuitsFlags { 

    None =     0,

    Spades =   1 << 0, 
    Clubs =    1 << 1, 
    Diamonds = 1 << 2, 
    Hearts =   1 << 3,

    All =      ~(~0 << 4)

}

Pemakaian:

Spades | Clubs | Diamonds | Hearts == All  // true
Spades & Clubs == None  // true

 
Pembaruan 2019-10:

Karena C # 7.0 Anda dapat menggunakan literal biner, yang mungkin lebih intuitif untuk dibaca:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

    Spades =   0b0001, 
    Clubs =    0b0010, 
    Diamonds = 0b0100, 
    Hearts =   0b1000,

    All =      0b1111

}
Jpsy
sumber
17

Untuk menambahkan Mode.Write:

Mode = Mode | Mode.Write;
David Wengier
sumber
57
atau Mode | = Mode.Write
abatishchev
14

Ada sesuatu yang terlalu verbose kepada saya tentang if ((x & y) == y)...membangun, terutama jika xDAN ykeduanya set senyawa bendera dan Anda hanya ingin tahu apakah ada setiap tumpang tindih.

Dalam hal ini, yang benar-benar perlu Anda ketahui adalah jika ada nilai non-nol [1] setelah Anda bitmasked .

[1] Lihat komentar Jaime. Jika kami benar-benar bitmasking , kami hanya perlu memeriksa bahwa hasilnya positif. Tapi karena enums bisa menjadi negatif, bahkan, aneh, bila dikombinasikan dengan yang [Flags] atribut , itu defensif untuk kode untuk != 0daripada > 0.

Membangun dari penyiapan @ 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();
        }
    }
}
ruffin
sumber
Enum mungkin didasarkan pada jenis yang ditandatangani, jadi Anda harus menggunakan "! = 0" alih-alih "> 0".
gagak
@JaimePardos - Selama kita menjaga mereka byte yang jujur, seperti yang saya lakukan dalam contoh ini, tidak ada konsep negatif. Hanya 0 hingga 255. Seperti yang diperingatkan MSDN , "Gunakan hati-hati jika Anda mendefinisikan angka negatif sebagai flag yang disebutkan konstan karena ... [itu] mungkin membuat kode Anda membingungkan dan mendorong kesalahan pengkodean." Sungguh aneh berpikir dalam istilah "bitflag negatif"! ; ^) Saya akan mengedit lebih banyak dalam sedikit. Tapi Anda benar, jika kami menggunakan nilai negatif dalam enum, kami harus memeriksanya != 0.
ruffin
10

Bendera memungkinkan Anda untuk menggunakan bitmasking di dalam enumerasi Anda. Ini memungkinkan Anda untuk menggabungkan nilai enumerasi, sambil mempertahankan yang ditentukan.

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

Mohon maaf jika seseorang sudah memperhatikan skenario ini. Contoh sempurna dari bendera yang dapat kita lihat dalam refleksi. Ya Mengikat Bendera 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

Pemakaian

             // 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 [] {});
kbvishnu
sumber