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