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



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



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


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



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

1058   5  

5 ответов:

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

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

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

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

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

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

для декодирования битовых масок вы и ваше значение с маской, Вот так:

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

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

Comments

    Ничего не найдено.