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]?

steffenj
sumber
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
Hugoware
sumber
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)
Drew Noakes
sumber
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);
Stephen Deken
sumber
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.

Chuck Dee
sumber
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 .

TylerBrinkley
sumber
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))
Petesh
sumber
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;
        }
    }
}
Mark Bamford
sumber
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;

Nashirak
sumber
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];

  }
}
Tony Tanzillo
sumber
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

workmad3
sumber
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