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

Скажем, у меня есть следующее

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

и я передаю 10 (8 + 2) в качестве параметра методу, и я хочу его декодировать как означающий susan и karen

Я знаю, что 10 равно 1010

но как я могу сделать некоторую логику, чтобы проверить, проверен ли конкретный бит, как в

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

Сейчас все, о чем я могу думать, это проверить, прошел ли номер i

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

Когда у меня есть большее количество реальных бит в моем реальном мире, это кажется непрактичным, что лучше использовать маску, чтобы просто проверить, соответствует ли мне условие только для karen?

Я могу думать о переносе влево, затем обратно, а затем переходить прямо назад, чтобы очистить биты, отличные от того, что меня интересует, но это также кажется слишком сложным.

Ответ 1

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

Ответ 2

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

Побитовое "и" замаскирует все, кроме бит, который "представляет" Карен. Пока каждый человек представлен одной битовой позицией, вы можете проверить несколько людей простым:

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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