Menggunakan bitmask di C #

99

Katakanlah saya memiliki yang berikut ini

int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000

dan saya meneruskan 10 (8 + 2) sebagai parameter ke metode dan saya ingin memecahkan kode ini menjadi susan dan karen

Saya tahu bahwa 10 adalah 1010

tetapi bagaimana saya bisa melakukan logika untuk melihat apakah bit tertentu dicentang seperti pada

if (condition_for_karen) // How to quickly check whether effective karen bit is 1

Saat ini yang bisa saya pikirkan adalah memeriksa apakah nomor yang saya lulus adalah

14 // 1110
12 // 1100
10 // 1010
8 //  1000

Ketika saya memiliki jumlah bit aktual yang lebih besar dalam skenario dunia nyata saya, ini sepertinya tidak praktis, cara apa yang lebih baik menggunakan topeng untuk sekadar memeriksa apakah saya memenuhi syarat untuk sekedar karen?

Saya dapat memikirkan untuk menggeser ke kiri lalu ke belakang lalu menggeser ke kanan lalu kembali ke bagian yang jelas selain yang saya minati, tetapi ini juga tampak terlalu rumit.

Matt
sumber
7
Hanya harus mengomentari penggunaan. Jika Anda melakukan operasi bit, Anda sebaiknya hanya menggunakan operator manipulasi bit. yaitu, anggap saja sebagai (8 | 2), bukan (8 + 2).
Jeff Mercado
Karen juga ingin berbicara dengan manajer Anda, segera.
Krythic

Jawaban:

199

Cara tradisional untuk melakukannya adalah dengan menggunakan Flagsatribut pada enum:

[Flags]
public enum Names
{
    None = 0,
    Susan = 1,
    Bob = 2,
    Karen = 4
}

Kemudian Anda akan memeriksa nama tertentu sebagai berikut:

Names names = Names.Susan | Names.Bob;

// evaluates to true
bool susanIsIncluded = (names & Names.Susan) != Names.None;

// evaluates to false
bool karenIsIncluded = (names & Names.Karen) != Names.None;

Kombinasi bitwise yang logis bisa jadi sulit untuk diingat, jadi saya membuat hidup saya lebih mudah dengan FlagsHelperkelas *:

// The casts to object in the below code are an unfortunate necessity due to
// C#'s restriction against a where T : Enum constraint. (There are ways around
// this, but they're outside the scope of this simple illustration.)
public static class FlagsHelper
{
    public static bool IsSet<T>(T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        return (flagsValue & flagValue) != 0;
    }

    public static void Set<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue | flagValue);
    }

    public static void Unset<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue & (~flagValue));
    }
}

Ini akan memungkinkan saya untuk menulis ulang kode di atas sebagai:

Names names = Names.Susan | Names.Bob;

bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);

bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);

Catatan Saya juga bisa menambahkan Karenke set dengan melakukan ini:

FlagsHelper.Set(ref names, Names.Karen);

Dan saya bisa menghapus Susandengan cara yang sama:

FlagsHelper.Unset(ref names, Names.Susan);

* Sebagai Porges menunjukkan, setara dengan yang IsSetmetode di atas sudah ada di NET 4.0: Enum.HasFlag. Namun Set, Unsetmetode dan tampaknya tidak memiliki padanan; jadi saya masih akan mengatakan kelas ini memiliki beberapa manfaat.


Catatan: Menggunakan enum hanyalah cara konvensional untuk mengatasi masalah ini. Anda benar-benar dapat menerjemahkan semua kode di atas untuk menggunakan int sebagai gantinya dan itu akan bekerja dengan baik.

Dan Tao
sumber
14
1 untuk menjadi kode pertama yang benar-benar berfungsi. Anda juga dapat melakukannya (names & Names.Susan) == Names.Susan, yang tidak memerlukan file None.
Matthew Flaschen
1
@ Matius: Oh ya, poin yang bagus. Saya kira saya baru saja terbiasa untuk selalu mendefinisikan Nonenilai untuk semua enum saya, karena saya merasa itu akhirnya menjadi nyaman dalam banyak skenario.
Dan Tao
31
Ini sudah ada di var susanIsIncluded = names.HasFlag(Names.Susan);
dalamnya
2
@Porges: Wow, tidak tahu bagaimana saya melewatkan itu ... terima kasih telah menunjukkannya! (Sepertinya ini hanya tersedia pada .NET 4.0, meskipun ... juga, tidak ada padanan untuk Setmetode ini. Jadi, menurut saya metode pembantu setidaknya tidak sepenuhnya tidak berharga.)
Dan Tao
6
Perhatikan, bahwa menggunakan names.HasFlag(Names.Susan)seperti (names & Names.Susan) == Names.Susanitu tidak selalu seperti itu (names & Names.Susan) != Names.None. Misalnya jika Anda akan memeriksa apakah names.HasFlag(Names.none)ataunames.HasFlag(Names.Susan|Names.Karen)
ABCade
20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Bitwise 'and' akan menutupi semuanya kecuali bit yang "mewakili" Karen. Selama setiap orang diwakili oleh satu posisi bit, Anda dapat memeriksa beberapa orang dengan cara sederhana:

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}
eldarerathis
sumber
12

Saya telah menyertakan contoh di sini yang menunjukkan bagaimana Anda dapat menyimpan mask di kolom database sebagai int, dan bagaimana Anda akan mengembalikan mask nanti:

public enum DaysBitMask { Mon=0, Tues=1, Wed=2, Thu = 4, Fri = 8, Sat = 16, Sun = 32 }


DaysBitMask mask = DaysBitMask.Sat | DaysBitMask.Thu;
bool test;
if ((mask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((mask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((mask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

// Store the value
int storedVal = (int)mask;

// Reinstate the mask and re-test
DaysBitMask reHydratedMask = (DaysBitMask)storedVal;

if ((reHydratedMask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((reHydratedMask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((reHydratedMask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;
Nick Wright
sumber
Saya melakukan sesuatu yang serupa tetapi ketika menentukan topeng saya melakukan Mon = Math.Power (2, 0), Sel = Math.Pow (2, 1), Wed = Math.Pow (2, 2), dll. Jadi posisi bit sedikit lebih jelas bagi pengguna yang tidak terbiasa dengan konversi biner ke desimal. Blindy juga bagus karena berubah menjadi hasil boolean dengan menggeser bit bertopeng.
Analog Arsonist
7

Untuk menggabungkan bitmask Anda ingin menggunakan bitwise- atau . Dalam kasus sepele di mana setiap nilai yang Anda gabungkan memiliki tepat 1 bit (seperti contoh Anda), itu setara dengan menambahkannya. Namun, jika Anda memiliki bit yang tumpang tindih, atau mereka menangani kasing dengan baik.

Untuk memecahkan kode bitmask Anda dan nilai Anda dengan mask, seperti ini:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
Buta
sumber
1
Anda tidak dapat menggunakan integer sebagai boolean di C #.
Bayangan
7

Jalan mudah:

[Flags]
public enum MyFlags {
    None = 0,
    Susan = 1,
    Alice = 2,
    Bob = 4,
    Eve = 8
}

Untuk menyetel tanda, gunakan operator "atau" logika |:

MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;

Dan untuk memeriksa apakah sebuah bendera disertakan, gunakan HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
A-Sharabiani
sumber
Sepertinya semua info ini sudah disediakan di atas. Jika Anda memberikan info baru, Anda harus menandainya dengan jelas.
sonyisda1
1
contoh sederhana penggunaan the HasFlag()dan the [Flags]tidak tersedia di jawaban lain.
A-Sharabiani
0

Satu alasan lain yang sangat bagus untuk menggunakan bitmask vs individual bools adalah sebagai pengembang web, ketika mengintegrasikan satu situs web ke situs web lain, kita sering kali perlu mengirim parameter atau tanda di string kueri. Selama semua flag Anda adalah biner, akan lebih mudah menggunakan satu nilai sebagai bitmask daripada mengirim banyak nilai sebagai bools. Saya tahu ada cara lain untuk mengirim data (GET, POST, dll.), Tetapi parameter sederhana pada querystring seringkali cukup untuk item yang tidak sensitif. Cobalah untuk mengirim 128 nilai bool pada querystring untuk berkomunikasi dengan situs eksternal. Ini juga memberikan kemampuan tambahan untuk tidak memaksakan batasan pada querystring url di browser

Greg Osborne
sumber
Bukan jawaban untuk pertanyaan OP-- seharusnya sebuah komentar.
sonyisda1