Скажем, у меня есть следующее
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
Прямо сейчас все, о чем я могу думать, должно проверить, является ли число, которое я передал,
14 // 1110
12 // 1100
10 // 1010
8 // 1000
Когда у меня есть большее число фактических битов в моем сценарии реального мира, это кажется непрактичным, что лучший путь использует маску, чтобы просто проверить, удовлетворяю ли я условию для просто karen?
Я могу думать о смещении, оставленном затем обратно затем смещение прямо тогда назад для очистки битов кроме того, которым я интересуюсь, но это также кажется чрезмерно сложным.
Традиционный способ сделать это - использовать атрибут Flags
в перечислении
:
[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);
Примечание. Я мог бы также добавить Карен
в набор, выполнив следующие действия:
FlagsHelper.Set(ref names, Names.Karen);
И я мог бы удалить Сьюзан
аналогичным образом:
FlagsHelper.Unset(ref names, Names.Susan);
* Как указал Поргес, эквивалент из приведенного выше метода IsSet
уже существует в .NET 4.0: Enum.HasFlag
. Однако методы Set
и Unset
, похоже, не имеют эквивалентов; так что я бы все же сказал, что у этого класса есть некоторые достоинства.
Примечание. Использование перечислений - это просто обычный способ решения этой проблемы. Вы можете полностью перевести весь приведенный выше код на использование целых чисел, и он будет работать так же хорошо.
if ( ( param & karen ) == karen )
{
// Do stuff
}
Поразрядное «и» замаскирует все, кроме бита, который «представляет» Карен. Пока каждый человек представлен одной битовой позицией, вы можете проверить несколько людей с помощью простого:
if ( ( param & karen ) == karen )
{
// Do Karen's stuff
}
if ( ( param & bob ) == bob )
// Do Bob's stuff
}
Чтобы комбинировать битовые маски, которые вы хотите использовать побитово - или ]. В тривиальном случае, когда каждое объединенное вами значение имеет ровно 1 бит (как в вашем примере), это эквивалентно их добавлению. Однако, если у вас есть перекрывающиеся биты, или 'их обработка корректно обрабатывает регистр.
Чтобы декодировать битовые маски, вы и ваше значение с помощью маски, например:
if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();