假设我有以下
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
当我在我的真实场景中拥有更多的实际比特时,这似乎是不切实际的,使用掩码来检查我是否满足karen条件的更好方法是什么?
我可以想到向左移动然后向后移动然后向右移动然后移回到除了我感兴趣的那个之外的其他位置,但这似乎也过于复杂。
答案 0 :(得分:175)
执行此操作的传统方法是在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);
*正如Porges所指出的,.NET 4.0中已经存在上述IsSet
方法的等价物:Enum.HasFlag
。但Set
和Unset
方法似乎没有等价物;所以我仍然说这堂课有一些优点。
注意:使用枚举只是解决此问题的传统方式。您可以将所有上述代码完全翻译为使用ints,它也可以正常工作。
答案 1 :(得分:20)
if ( ( param & karen ) == karen )
{
// Do stuff
}
按位'和'将屏蔽除“代表”Karen的位之外的所有内容。只要每个人都由一个比特位置代表,您就可以通过简单的方式检查多个人:
if ( ( param & karen ) == karen )
{
// Do Karen's stuff
}
if ( ( param & bob ) == bob )
// Do Bob's stuff
}
答案 2 :(得分:11)
我在这里添加了一个示例,演示了如何将掩码作为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;
答案 3 :(得分:7)
组合要使用bitwise- 或的位掩码。在简单的情况下,你组合的每个值都只有1位(就像你的例子),它相当于添加它们。但是,如果你有重叠位,那么或可以优雅地处理这个案例。
使用掩码解码你和你的值的位掩码,如下所示:
if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
答案 4 :(得分:1)
简便方法:
[Flags]
public enum MyFlags {
None = 0,
Susan = 1,
Alice = 2,
Bob = 4,
Eve = 8
}
要设置标志,请使用逻辑“或”运算符|
:
MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;
要检查是否包含标志,请使用HasFlag
:
if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
答案 5 :(得分:0)
使用位掩码与单个bools的另一个非常好的理由是作为Web开发人员,当将一个网站集成到另一个网站时,我们经常需要在查询字符串中发送参数或标志。只要所有标志都是二进制,就可以使用单个值作为位掩码,而不是将多个值作为bools发送。我知道还有其他方法可以发送数据(GET,POST等),但查询字符串上的一个简单参数大部分时间都足以用于非敏感项。尝试在查询字符串上发送128个bool值以与外部站点进行通信。这也增加了不在浏览器中限制网址查询字符串的能力