[英]Using a bitmask in 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
当我在我的真实场景中拥有更多的实际比特时,这似乎是不切实际的,使用掩码来检查我是否满足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
。 但是, Set
和Unset
方法似乎没有等价物; 所以我仍然说这堂课有一些优点。
注意:使用枚举只是解决此问题的传统方法。 您可以完全翻译所有上述代码以使用ints,它也可以正常工作。
if ( ( param & karen ) == karen )
{
// Do stuff
}
按位'和'将屏蔽除“代表”Karen的位之外的所有内容。 只要每个人都由一个位位置代表,您就可以通过简单的方式检查多个人:
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的另一个非常好的理由是作为Web开发人员,当将一个网站集成到另一个网站时,我们经常需要在查询字符串中发送参数或标志。 只要所有标志都是二进制,就可以使用单个值作为位掩码,而不是将多个值作为bools发送。 我知道还有其他方法可以发送数据(GET,POST等),但查询字符串上的一个简单参数大部分时间都足以用于非敏感项。 尝试在查询字符串上发送128个bool值以与外部站点进行通信。 这也增加了在浏览器中不限制url查询字符串的能力
简单的方法:
[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 */}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.