Operasi bitwise C # paling umum pada enum


201

Untuk kehidupan saya, saya tidak ingat bagaimana mengatur, menghapus, beralih atau menguji sedikit di bitfield. Entah saya tidak yakin atau saya mencampurnya karena saya jarang membutuhkan ini. Jadi "bit-cheat-sheet" akan menyenangkan untuk dimiliki.

Sebagai contoh:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

atau

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

Bisakah Anda memberikan contoh dari semua operasi umum lainnya, lebih disukai dalam sintaks C # menggunakan enum [Bendera]?


5
Ini telah dijawab sebelumnya di sini
Greg Rogers

7
Sayang sekali tautan itu tidak muncul di petunjuk pertanyaan untuk topik ini.
cori

10
Pertanyaan itu ditandai untuk c / c ++, jadi seseorang yang mencari info tentang C # mungkin tidak akan mencari di sana meskipun sintaksnya tampaknya sama.
Adam Lassek

Saya tidak mengetahui cara yang kurang jelas untuk melakukan tes bit
Andy Johnson

2
@Andy, ada API untuk uji bit di .NET 4 sekarang.
Drew Noakes

Jawaban:


288

Saya melakukan beberapa pekerjaan pada ekstensi ini - Anda dapat menemukan kode di sini

Saya menulis beberapa metode ekstensi yang memperpanjang System.Enum yang sering saya gunakan ... Saya tidak mengklaim bahwa mereka antipeluru, tetapi mereka telah membantu ... Komentar dihapus ...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

Kemudian mereka digunakan seperti berikut ini

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false

1
Saya juga menemukan ini berguna - Ada ide bagaimana saya bisa memodifikasinya sehingga bekerja pada semua tipe yang mendasarinya?
Charlie Garts

7
Ekstensi ini hanya membuat hari saya, minggu saya, bulan saya, dan sangat mungkin tahun saya.
thaBadDawg

Terima kasih! Semua orang: pastikan untuk memeriksa pembaruan yang telah ditautkan oleh Hugoware.
Helge Klein

Satu set ekstensi yang sangat bagus. Sayang sekali mereka membutuhkan tinju, meskipun saya tidak bisa memikirkan alternatif yang tidak menggunakan tinju dan ini ringkas. Bahkan HasFlagmetode baru pada Enummembutuhkan tinju.
Drew Noakes

4
@Drew: Lihat code.google.com/p/unconstrained-melody untuk menghindari tinju :)
Jon Skeet

109

Di .NET 4 Anda sekarang dapat menulis:

flags.HasFlag(FlagsEnum.Bit4)

4
+1 untuk menunjukkan hal itu, meskipun FlagsEnumnama yang jelek. :)
Jim Schubert

2
@ Jim, mungkin. Itu hanya nama sampel, seperti yang digunakan dalam pertanyaan awal, jadi Anda bebas mengubahnya dalam kode Anda.
Drew Noakes

14
Aku tahu! Tapi nama-nama jelek seperti IE6 dan mungkin tidak akan pernah hilang :(
Jim Schubert

5
@ JimSchubert, sekali lagi, saya baru saja mereproduksi nama tipe dari pertanyaan awal agar tidak membingungkan masalah. The NET Pencacahan Jenis Penamaan Pedoman menunjukkan bahwa semua [Flags]enum harus memiliki nama pluralised, sehingga nama FlagsEnummemiliki bahkan masalah yang lebih serius daripada keburukan.
Drew Noakes

1
Saya juga merekomendasikan Pedoman Kerangka Desain: Konvensi, Idiom, dan Pola untuk Reusable .NET Libraries . Agak mahal untuk membelinya, tapi saya percaya Safari Online dan Books24x7 keduanya menawarkannya untuk pelanggan.
Jim Schubert

89

Idiomnya adalah menggunakan operator bitwise atau-sama untuk mengatur bit:

flags |= 0x04;

Untuk menghapus sedikit, idiomnya adalah menggunakan bitwise dan dengan negasi:

flags &= ~0x04;

Kadang-kadang Anda memiliki offset yang mengidentifikasi bit Anda, dan kemudian idiomnya adalah menggunakan ini dikombinasikan dengan shift kiri:

flags |= 1 << offset;
flags &= ~(1 << offset);

22

@Drew

Perhatikan bahwa kecuali dalam kasus yang paling sederhana, Enum.HasFlag membawa penalti performa yang berat dibandingkan dengan menuliskan kode secara manual. Pertimbangkan kode berikut:

[Flags]
public enum TestFlags
{
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256,
    Ten = 512
}


class Program
{
    static void Main(string[] args)
    {
        TestFlags f = TestFlags.Five; /* or any other enum */
        bool result = false;

        Stopwatch s = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; i++)
        {
            result |= f.HasFlag(TestFlags.Three);
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*

        s.Restart();
        for (int i = 0; i < 10000000; i++)
        {
            result |= (f & TestFlags.Three) != 0;
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*        

        Console.ReadLine();
    }
}

Lebih dari 10 juta iterasi, metode ekstensi HasFlags mengambil 4793 ms kekalahan, dibandingkan dengan 27 ms untuk implementasi bitwise standar.


10
Meskipun tentu saja menarik dan bagus untuk ditunjukkan. Anda harus mempertimbangkan penggunaannya. Menurut ini jika Anda tidak melakukan beberapa ratus ribu ops atau lebih, Anda mungkin tidak akan menyadarinya.
Joshua Hayes

7
The HasFlagMetode melibatkan tinju / unboxing, yang menyumbang perbedaan ini. Tetapi biayanya sangat sepele (0,4 μs) sehingga kecuali Anda berada dalam lingkaran yang ketat, saya akan menerima panggilan deklaratif API yang lebih mudah dibaca (dan kurang memungkinkan buggy) setiap hari.
Drew Noakes

8
Tergantung pada penggunaan, itu bisa menjadi masalah. Dan karena saya bekerja dengan loader sedikit, saya pikir itu bagus untuk ditunjukkan.
Chuck Dee

11

Sayangnya, operasi flag enum bawaan NET. Cukup terbatas. Sebagian besar waktu pengguna dibiarkan mencari tahu logika operasi bitwise.

Dalam. NET 4, metode HasFlagini ditambahkan ke Enumyang membantu menyederhanakan kode pengguna tetapi sayangnya ada banyak masalah dengannya.

  1. HasFlag bukan tipe-aman karena menerima semua jenis argumen nilai enum, bukan hanya tipe enum yang diberikan.
  2. HasFlagtidak jelas apakah memeriksa apakah nilai memiliki semua atau salah satu flag yang disediakan oleh argumen nilai enum. Semua itu omong-omong.
  3. HasFlag agak lambat karena membutuhkan tinju yang menyebabkan alokasi dan dengan demikian lebih banyak pengumpulan sampah.

Sebagian karena .NET dukungan terbatas untuk flag enums saya menulis perpustakaan OSS Enums.NET yang membahas masing-masing masalah ini dan membuat berurusan dengan flag enums lebih mudah.

Di bawah ini adalah beberapa operasi yang disediakan beserta implementasinya yang setara hanya dengan menggunakan .NET framework.

Gabungkan Bendera

.BERSIH             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Hapus Bendera

.BERSIH             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Bendera umum

.BERSIH             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Toggle Flags

.BERSIH             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Memiliki Semua Bendera

.NET             (flags & otherFlags) == otherFlags atauflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


Memiliki Bendera Apa Pun

.BERSIH             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Dapatkan Bendera

.BERSIH

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


Saya mencoba untuk mendapatkan perbaikan ini dimasukkan ke dalam. NET Core dan mungkin akhirnya .NET Framework. Anda dapat memeriksa proposal saya di sini .


7

Sintaks C ++, dengan asumsi bit 0 adalah LSB, dengan asumsi flag tidak ditandai:

Periksa apakah Set:

flags & (1UL << (bit to test# - 1))

Periksa apakah tidak disetel:

invert test !(flag & (...))

Set:

flag |= (1UL << (bit to set# - 1))

Bersih:

flag &= ~(1UL << (bit to clear# - 1))

Beralih:

flag ^= (1UL << (bit to set# - 1))

3

Untuk kinerja terbaik dan nol sampah, gunakan ini:

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}

2

Untuk menguji sedikit Anda akan melakukan hal berikut: (dengan asumsi bendera adalah angka 32 bit)

Bit Uji:

if((flags & 0x08) == 0x08)
(Jika bit 4 disetel maka itu benar) Toggle Back (1 - 0 atau 0 - 1):
flags = flags ^ 0x08;
Setel Ulang Bit 4 ke Nol:
flags = flags & 0xFFFFFF7F;


2
-1 karena ini bahkan tidak repot dengan enum? Plus, mengkodekan tangan nilainya rapuh ... Saya setidaknya akan menulis ~0x08alih-alih 0xFFFFFFF7... (topeng sebenarnya untuk 0x8)
Ben Mosher

1
Pada awalnya saya berpikir bahwa Ben -1 adalah keras, tetapi penggunaan "0xFFFFFF7F" membuat ini menjadi contoh yang sangat buruk.
ToolmakerSteve

2

Ini terinspirasi oleh menggunakan Sets sebagai pengindeks di Delphi, jalan kembali ketika:

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}

2
Ini bahkan tidak dapat dikompilasi jika BindingFlags adalah byte enum: this.flags & = ~ index;
Amuliar

0

Operasi C ++ adalah: & | ^ ~ (untuk dan, atau, xor dan bukan operasi bitwise). Yang juga menarik adalah >> dan <<, yang merupakan operasi bitshift.

Jadi, untuk menguji bit yang diatur dalam sebuah flag, Anda akan menggunakan: if (flags & 8) // tes bit 4 telah ditetapkan


8
Pertanyaan terkait dengan c #, bukan c ++
Andy Johnson

3
Di sisi lain, C # menggunakan operator yang sama: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
Untuk mempertahankan @ workmad3, tag asli berisi C dan C ++
pqsk
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.