Использование битовой маски в C #

99

Допустим, у меня есть следующее

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

Ответы:

199

Традиционный способ сделать это - использовать 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методы , кажется , не имеет аналогов, хотя; так что я бы все же сказал, что у этого класса есть некоторые достоинства.


Примечание. Использование перечислений - это обычный способ решения этой проблемы. Вы можете полностью перевести весь приведенный выше код на использование целых чисел, и он будет работать так же хорошо.

Дэн Тао
источник
14
+1 за то, что это первый реально работающий код. Вы также можете сделать это (names & Names.Susan) == Names.Susan, для чего не требуется None.
Matthew Flaschen
1
@ Мэтью: Ах да, хороший момент. Полагаю, я просто привык всегда определять Noneзначение для всех моих перечислений, так как считаю, что это удобно во многих сценариях.
Дэн Тао
31
Это встроено, вам не нужны ваши вспомогательные методы ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges
2
@Porges: Вау, понятия не имею, как я это пропустил ... Спасибо, что указали на это! (Похоже, что он доступен только в .NET 4.0, хотя… также нет эквивалента для этого Setметода. Так что, я бы сказал, что вспомогательные методы, по крайней мере, не совсем бесполезны.)
Дэн Тао,
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), Вт = Math.Pow (2, 1), Wed = Math.Pow (2, 2) и т. Д., Так что позиция бита немного более очевиден для тех, кто не привык к преобразованию двоичного кода в десятичный. Blindy's тоже хорош, так как он превращается в логический результат путем сдвига замаскированного бита.
Analog Arsonist
7

Чтобы комбинировать битовые маски, вы хотите использовать побитовое или . В тривиальном случае, когда каждое объединенное вами значение имеет ровно 1 бит (как в вашем примере), это эквивалентно их добавлению. Однако, если у вас есть перекрывающиеся биты, или их использование изящно обрабатывает случай.

Чтобы декодировать битовые маски, вы и ваше значение с помощью маски, например:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
Слепой
источник
1
Вы не можете использовать целое число в качестве логического в C #.
Shadow
7

Простой способ:

[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]не был приведен в других ответах.
А-Шарабиани,
0

Еще одна действительно веская причина использовать битовую маску вместо отдельных bools - это, как веб-разработчик, когда мы интегрируем один веб-сайт с другим, нам часто нужно отправлять параметры или флаги в строке запроса. Поскольку все ваши флаги являются двоичными, это значительно упрощает использование одного значения в качестве битовой маски, чем отправку нескольких значений в виде логических значений. Я знаю, что есть другие способы отправки данных (GET, POST и т. Д.), Но для нечувствительных элементов в большинстве случаев достаточно простого параметра в строке запроса. Попробуйте отправить 128 значений типа bool в строке запроса для связи с внешним сайтом. Это также дает дополнительную возможность не нажимать ограничение на строки запроса URL в браузерах.

Грег Осборн
источник
Не совсем ответ на вопрос ОП - должен был быть комментарий.
sonyisda1