Использование битовой маски в C # - PullRequest
85 голосов
/ 16 июля 2010

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

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

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

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

Ответы [ 5 ]

179 голосов
/ 16 июля 2010

Традиционный способ сделать это - использовать атрибут 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);

* Как указывал Поргес, в * .NET 4.0 уже существует эквивалент IsSet метода, описанного выше: Enum.HasFlag. Методы Set и Unset, похоже, не имеют эквивалентов; так что я бы все еще сказал, что у этого класса есть некоторые достоинства.


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

20 голосов
/ 16 июля 2010
if ( ( param & karen ) == karen )
{
  // Do stuff
}

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

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}
11 голосов
/ 17 мая 2012

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

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

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

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
0 голосов
/ 03 февраля 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...