Використання бітової маски в C #


97

Скажімо, у мене є таке

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

і я передаю 10 (8 + 2) як параметр методу, і я хочу розшифрувати це як означає Сьюзен та Карен

Я знаю, що 10 - це 1010

але як я можу зробити якусь логіку, щоб перевірити, чи перевірено певний біт як у

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

Зараз все, що я можу придумати, - це перевірити, чи це число, яке я передав

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

Коли в моєму реальному сценарії у мене більша кількість фактичних бітів, це здається недоцільним, що є кращим способом використання маски, щоб просто перевірити, чи відповідаю я умові лише для карен?

Я можу подумати про перехід вліво, потім назад, потім вправо, потім назад, щоб очистити біти, крім того, який мене цікавить, але це також здається надто складним.


7
Просто довелося прокоментувати використання. Якщо ви виконуєте бітові операції, слід використовувати лише оператори, що керують бітами. тобто думайте про це як (8 | 2), а не (8 + 2).
Джефф Меркадо,

Карен також хотіла б негайно поговорити з вашим менеджером.
Krythic

Відповіді:


198

Традиційний спосіб зробити це - використовувати Flagsатрибут на enum:

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

Потім ви перевіряєте наявність певного імені таким чином:

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;

Логічні розрядні комбінації важко запам’ятати, тому я полегшую собі життя за допомогою FlagsHelperкласу *:

// 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));
    }
}

Це дозволило б мені переписати наведений вище код як:

Names names = Names.Susan | Names.Bob;

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

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

Примітка. Я також можу додати Karenдо набору, виконавши це:

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

І я міг би видалити Susanподібним чином:

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

* Як Поргеса зазначив, еквівалент цього IsSetметоду вище вже існує в .NET 4.0: Enum.HasFlag. SetІ Unsetметоди , здається , не має аналогів, хоча; тому я все-таки сказав би, що цей клас має певні переваги.


Примітка. Використання перелічень - це звичайний спосіб вирішення цієї проблеми. Ви можете повністю перекласти весь вищезазначений код, а замість нього використовувати ints, і він буде працювати так само добре.


14
+1 за те, що це перший код, який насправді працює. Ви також можете зробити (names & Names.Susan) == Names.Susan, що не вимагає None.
Matthew Flaschen

1
@Matthew: О так, гарна думка. Я здогадуюсь, у мене просто є звичка завжди визначати Noneзначення для всіх моїх переліків, оскільки, як мені здається, це зручно в багатьох сценаріях.
Dan Tao

30
Це вбудовано, вам не потрібні ваші допоміжні методи ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@Porges: Ого, не знаю, як я це пропустив ... дякую, що вказав на це! (Схоже, він доступний лише станом на .NET 4.0, хоча ... також, для Setметоду немає еквівалента . Отже, я б сказав, що допоміжні методи принаймні не зовсім марні.)
Dan Tao

6
Зауважте, що використання names.HasFlag(Names.Susan)подібне, (names & Names.Susan) == Names.Susanщо не завжди подібне (names & Names.Susan) != Names.None. Наприклад, якщо ви перевірите, names.HasFlag(Names.none)чиnames.HasFlag(Names.Susan|Names.Karen)
ABCade

20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Побітове 'і' замаскує все, крім біта, який "представляє" Карен. Поки кожна особа представлена ​​єдиною бітовою позицією, ви можете перевірити кількох людей за допомогою простого:

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

12

Я включив тут приклад, який демонструє, як ви можете зберігати маску в стовпці бази даних як int, і як ви відновите маску пізніше:

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;

Я зробив щось подібне, але при визначенні маски я зробив Mon = Math.Power (2, 0), Tuesday = Math.Pow (2, 1), Wed = Math.Pow (2, 2) і т. Д., Тому позиція біта є трохи більш очевидним для тих, хто користується, хто не звик до двійкового перетворення в десяткове. Blindy's теж хороший, оскільки він перетворюється на логічний результат, переміщуючи маскований біт.
Аналоговий підпальник

7

Щоб об'єднати бітові маски, які ви хочете використовувати побітове або . У тривіальному випадку, коли кожне об’єднане вами значення має рівно 1 біт (як ваш приклад), це еквівалентно їх додаванню. Однак якщо у вас є біти, що перекриваються, або їх використання обробляє справу витончено.

Щоб декодувати бітові маски вас і ваше значення за допомогою маски, приблизно так:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();

1
Ви не можете використовувати ціле число як логічне значення в C #.
Тінь

6

Легкий шлях:

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

Щоб встановити прапори, використовуйте логічний оператор "або" |:

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

А щоб перевірити, чи входить прапор, використовуйте HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}

Здається, вся ця інформація вже була надана вище. Якщо ви надаєте якусь нову інформацію, ви повинні чітко її позначити.
sonyisda1

1
простий приклад використання значень HasFlag()та [Flags]не було надано в інших відповідях.
A-Sharabiani

0

Ще однією дійсно вагомою причиною використання бітової маски проти окремих булів є веб-розробник, коли при інтеграції одного веб-сайту до іншого нам часто потрібно надсилати параметри або прапори в рядку запитів. Поки всі ваші прапори є двійковими, це значно спрощує використання одного значення як бітової маски, ніж надсилання кількох значень як bools. Я знаю, що існують інші способи надсилання даних (GET, POST тощо), але простий параметр у рядку запитів більшу частину часу достатній для нечутливих елементів. Спробуйте надіслати 128 значень bool на рядок запиту для зв'язку з зовнішнім сайтом. Це також дає додаткову можливість не натискати обмеження на рядки запитів URL-адрес у браузерах


Насправді не відповідь на запитання ОП - мав бути коментар.
sonyisda1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.