Mengapa izin enum sering memiliki nilai 0, 1, 2, 4?

159

Mengapa orang selalu menggunakan nilai enum suka 0, 1, 2, 4, 8dan tidak 0, 1, 2, 3, 4?

Apakah ini ada hubungannya dengan operasi bit, dll?

Saya akan sangat menghargai cuplikan sampel kecil tentang bagaimana ini digunakan dengan benar :)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}
Pascal
sumber
1
kemungkinan duplikat Enum sebagai Bendera menggunakan, mengatur dan menggeser
Henk Holterman
25
Saya tidak setuju pada suara dupe.
zzzzBov
Cara UNIX untuk mengatur izin juga didasarkan pada logika yang sama.
Rudy
3
@ Pascal: Mungkin bermanfaat untuk membaca tentang Bitwise OR (dan Bitwise AND ), yang mewakili |(dan &) mewakili. Berbagai jawaban menganggap Anda sudah terbiasa dengannya.
Brian
2
@IAdapter saya bisa melihat mengapa Anda akan berpikir seperti itu, karena jawaban untuk keduanya sama, tetapi saya pikir pertanyaannya berbeda. Pertanyaan lain hanya meminta contoh atau penjelasan atribut Flags di C #. Pertanyaan ini tampaknya tentang konsep bendera bit, dan dasar-dasar di belakangnya.
Jeremy S

Jawaban:

268

Karena mereka adalah kekuatan dua dan saya dapat melakukan ini:

var permissions = Permissions.Read | Permissions.Write;

Dan mungkin nanti ...

if( (permissions & Permissions.Write) == Permissions.Write )
{
    // we have write access
}

Ini adalah bidang bit, di mana setiap set bit sesuai dengan beberapa izin (atau apa pun nilai yang disebutkan secara logis sesuai dengan). Jika ini didefinisikan sebagai 1, 2, 3, ...Anda tidak akan dapat menggunakan operator bitwise dengan cara ini dan mendapatkan hasil yang bermakna. Untuk menggali lebih dalam ...

Permissions.Read   == 1 == 00000001
Permissions.Write  == 2 == 00000010
Permissions.Delete == 4 == 00000100

Perhatikan polanya di sini? Sekarang jika kita mengambil contoh asli saya, yaitu,

var permissions = Permissions.Read | Permissions.Write;

Kemudian...

permissions == 00000011

Lihat? Baik bit Readdan Writesudah diatur, dan saya bisa memeriksanya secara independen (Perhatikan juga bahwa Deletebit tidak diatur dan karena itu nilai ini tidak memberikan izin untuk menghapus).

Hal ini memungkinkan seseorang untuk menyimpan beberapa flag dalam satu bidang bit.

Ed S.
sumber
2
@ Malcolm: Itu; myEnum.IsSet. Saya berpendapat bahwa ini adalah abstraksi yang sama sekali tidak berguna dan hanya berfungsi untuk mengurangi pengetikan, tetapi meh
Ed S.
1
Jawaban yang bagus, tetapi Anda harus menyebutkan mengapa atribut Flags diterapkan, dan ketika Anda tidak ingin menerapkan Flags ke beberapa enum juga.
Andy
3
@Andy: Sebenarnya, Flagsatribut tidak lebih dari memberi Anda 'pencetakan cantik' iirc. Anda dapat menggunakan nilai yang disebutkan sebagai bendera terlepas dari keberadaan atribut.
Ed S.
3
@detly: Karena jika pernyataan dalam C # memerlukan ekspresi boolean. 0bukan false; falseadalah false. Namun Anda bisa menulis if((permissions & Permissions.Write) > 0).
Ed S.
2
Alih-alih 'rumit' (permissions & Permissions.Write) == Permissions.Write, Anda sekarang dapat menggunakanenum.HasFlag()
Louis Kottmann
147

Jika masih tidak jelas dari jawaban lain, pikirkan seperti ini:

[Flags] 
public enum Permissions 
{   
   None = 0,   
   Read = 1,     
   Write = 2,   
   Delete = 4 
} 

hanyalah cara yang lebih singkat untuk menulis:

public enum Permissions 
{   
    DeleteNoWriteNoReadNo = 0,   // None
    DeleteNoWriteNoReadYes = 1,  // Read
    DeleteNoWriteYesReadNo = 2,  // Write
    DeleteNoWriteYesReadYes = 3, // Read + Write
    DeleteYesWriteNoReadNo = 4,   // Delete
    DeleteYesWriteNoReadYes = 5,  // Read + Delete
    DeleteYesWriteYesReadNo = 6,  // Write + Delete
    DeleteYesWriteYesReadYes = 7, // Read + Write + Delete
} 

Ada delapan kemungkinan tetapi Anda dapat mewakili mereka sebagai kombinasi dari hanya empat anggota. Jika ada enam belas kemungkinan maka Anda dapat mewakili mereka sebagai kombinasi dari hanya lima anggota. Jika ada empat miliar kemungkinan maka Anda dapat mewakili mereka sebagai kombinasi dari hanya 33 anggota! Jelas jauh lebih baik untuk memiliki hanya 33 anggota, masing-masing (kecuali nol) kekuatan dua, daripada mencoba menyebutkan empat miliar item dalam enum.

Eric Lippert
sumber
32
+1 untuk citra mental seorang enumdengan empat miliar anggota. Dan bagian yang menyedihkan adalah, mungkin seseorang di luar sana telah mencobanya.
Daniel Pryden
23
@DanielPryden Sebagai pembaca harian Daily WTF, saya akan mempercayainya.
lembut
1
2 ^ 33 = ~ 8,6 miliar. Untuk 4 miliar nilai berbeda, Anda hanya perlu 32 bit.
CVn
5
@ MichaelKjörling salah satu dari 33 adalah untuk 0 default
ratchet freak
@ MichaelKjörling: Agar adil, hanya ada 32 anggota yang memiliki kekuatan 2, karena 0 bukan kekuatan dua. Jadi "33 anggota, masing-masing kekuatan dua" tidak tepat benar (kecuali jika Anda dihitung 2 ** -infinitysebagai kekuatan dua).
Brian
36

Karena nilai-nilai ini mewakili lokasi bit unik dalam biner:

1 == binary 00000001
2 == binary 00000010
4 == binary 00000100

dll

1 | 2 == binary 00000011

EDIT:

3 == binary 00000011

3 dalam biner diwakili oleh nilai 1 di kedua tempat dan dua tempat. Ini sebenarnya sama dengan nilainya 1 | 2. Jadi ketika Anda mencoba menggunakan tempat biner sebagai bendera untuk mewakili beberapa keadaan, 3 biasanya tidak bermakna (kecuali ada nilai logis yang sebenarnya adalah kombinasi keduanya)

Untuk klarifikasi lebih lanjut, Anda mungkin ingin memperluas contoh enum Anda sebagai berikut:

[Flags]
public Enum Permissions
{
  None = 0,   // Binary 0000000
  Read = 1,   // Binary 0000001
  Write = 2,  // Binary 0000010
  Delete = 4, // Binary 0000100
  All = 7,    // Binary 0000111
}

Oleh karena itu dalam saya punya Permissions.All, saya juga secara implisit memiliki Permissions.Read, Permissions.Write, danPermissions.Delete

Chris Shain
sumber
dan apa masalahnya dengan 2 | 3?
Pascal
1
@ Pascal: Karena 3adalah 11biner, yaitu, tidak peta untuk satu set bit tunggal, sehingga Anda kehilangan kemampuan untuk memetakan 1 bit dalam posisi sewenang-wenang untuk nilai yang berarti.
Ed S.
8
@ Pascal dengan kata lain 2|3 == 1|3 == 1|2 == 3,. Jadi jika Anda memiliki nilai dengan biner 00000011, dan bendera Anda termasuk nilai-nilai 1, 2dan 3, maka Anda tidak akan tahu apakah nilai yang mewakili 1 and 3, 2 and 3, 1 and 2atau only 3. Itu membuatnya jauh kurang berguna.
yshavit
10
[Flags]
public Enum Permissions
{
    None   =    0; //0000000
    Read   =    1; //0000001
    Write  = 1<<1; //0000010
    Delete = 1<<2; //0000100
    Blah1  = 1<<3; //0001000
    Blah2  = 1<<4; //0010000
}

Saya pikir menulis seperti ini lebih mudah dimengerti dan dibaca, dan Anda tidak perlu menghitungnya.

Dozer
sumber
5

Ini digunakan untuk mewakili flag bit yang memungkinkan kombinasi nilai enum. Saya pikir ini lebih jelas jika Anda menulis nilai dalam notasi hex

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
JaredPar
sumber
4
@ Pascal: Mungkin ini lebih mudah dibaca oleh Anda pada saat ini, tetapi ketika Anda mendapatkan pengalaman melihat byte dalam hex menjadi kebiasaan. Dua digit dalam hex map menjadi satu byte map menjadi 8 bit (well ... byte biasanya 8 bit ... tidak selalu benar, tetapi untuk contoh ini tidak masalah menggeneralisasi).
Ed S.
5
@ Cepat cepat, apa yang Anda dapatkan saat Anda mengalikan 4194304dengan 2? Bagaimana dengan 0x400000? Ini jauh lebih mudah untuk dikenali 0x800000sebagai jawaban yang benar daripada 8388608, dan juga lebih mudah untuk mengetik nilai hex.
phoog
6
Sekilas, jauh lebih mudah untuk mengetahui apakah flag Anda diatur dengan benar (yaitu, kekuatan 2), jika Anda menggunakan hex. Apakah 0x10000kekuatan dua? Ya, itu dimulai dengan 1, 2, 4, atau 8 dan memiliki semua 0 setelahnya. Anda tidak perlu menerjemahkan secara mental 0x10 ke 16 (meskipun hal itu mungkin akan menjadi kebiasaan kedua pada akhirnya), anggap saja sebagai, "beberapa kekuatan 2".
Brian
1
Saya benar-benar dengan Jared tentang hal itu menjadi lebih mudah untuk dicatat dalam hex. Anda hanya menggunakan 1 2 4 8 dan shift
bevacqua
1
Secara pribadi, saya lebih suka menggunakan misalnya P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Saya tidak yakin apakah itu semacam lipat konstan bekerja di C # tetapi berfungsi dengan baik di C / C ++ (dan juga Java, saya pikir).
lembut
1

Ini benar-benar lebih dari sebuah komentar, tetapi karena itu tidak akan mendukung pemformatan, saya hanya ingin memasukkan metode yang telah saya gunakan untuk menyiapkan enumerasi bendera:

[Flags]
public enum FlagTest
{
    None = 0,
    Read = 1,
    Write = Read * 2,
    Delete = Write * 2,
    ReadWrite = Read|Write
}

Saya menemukan pendekatan ini sangat membantu selama pengembangan dalam kasus di mana Anda ingin menjaga bendera Anda dalam urutan abjad. Jika Anda menentukan Anda perlu menambahkan nilai bendera baru, Anda bisa memasukkannya secara abjad dan satu-satunya nilai yang harus Anda ubah adalah yang sekarang mendahului.

Namun, perlu diketahui bahwa begitu suatu solusi diterbitkan untuk sistem produksi apa pun (terutama jika enum terbuka tanpa sambungan ketat, seperti melalui layanan web), maka sangat disarankan untuk tidak mengubah nilai apa pun yang ada dalam enum.

Mike Guthrie
sumber
1

Banyak jawaban yang bagus untuk yang satu ini ... Saya hanya akan mengatakan .. jika Anda tidak suka, atau tidak dapat dengan mudah memahami apa yang <<sintaks coba katakan .. Saya pribadi lebih suka alternatif (dan berani saya katakan, gaya deklarasi enum langsung ) ...

typedef NS_OPTIONS(NSUInteger, Align) {
    AlignLeft         = 00000001,
    AlignRight        = 00000010,
    AlignTop          = 00000100,
    AlignBottom       = 00001000,
    AlignTopLeft      = 00000101,
    AlignTopRight     = 00000110,
    AlignBottomLeft   = 00001001,
    AlignBottomRight  = 00001010
};

NSLog(@"%ld == %ld", AlignLeft | AlignBottom, AlignBottomLeft);

LOG 513 == 513

Jauh lebih mudah (setidaknya bagi saya sendiri) untuk memahaminya. Sejajarkan yang ... jelaskan hasil yang Anda inginkan, dapatkan hasil yang Anda INGIN .. Tidak perlu "perhitungan".

Alex Gray
sumber