Использование битовой маски в 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.