在C#中使用位掩码
假设我有以下几点
int susan = 2; //0010 int bob = 4; //0100 int karen = 8; //1000
我把10(8 + 2)作为parameter passing给一个方法,我想把这个解码为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条件的更好方法是什么?
我可以考虑左移然后右移,然后移回除了我感兴趣的位以外的其他位,但是这似乎也过于复杂。
传统的做法是在enum
上使用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);
注意我也可以通过这样做来将Karen
添加到集合中:
FlagsHelper.Set(ref names, Names.Karen);
我可以用类似的方法移除Susan
:
FlagsHelper.Unset(ref names, Names.Susan);
正如Porges所指出的,.NET 4.0中已经存在一个与上面的IsSet
方法等价的方法: Enum.HasFlag
。 尽pipe如此, Set
和Unset
方法似乎没有等价的方法; 所以我仍然会说这个class有一些优点。
注意:使用枚举就是解决这个问题的传统方法。 你可以完全翻译所有上面的代码来使用整数,而且它也可以工作。
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();
另外一个很好的理由使用位掩码和个人布尔是作为一个Web开发人员,当一个网站集成到另一个,我们经常需要发送查询string中的参数或标志。 只要所有的标志都是二进制的,使用一个单一的值作为位掩码比发送多个值作为bools简单得多。 我知道还有其他方法可以发送数据(GET,POST等),但查询string上的一个简单参数大部分时间足够用于不敏感的项目。 尝试在查询string上发送128个bool值以与外部站点通信。 这也增加了不能在浏览器中对url查询字串进行限制的能力