Using a bitmask in C#

假设我有以下几点

int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000

我把10(8 + 2)作为参数传递给一个方法我想把它解码成 Susan 和 Karen

我知道10是1010

but how can I do some logic to see if a specific bit is checked as in

if (condition_for_karen) // How to quickly check whether effective karen bit is 1

现在我能想到的就是看看我通过的号码是不是

14 // 1110
12 // 1100
10 // 1010
8 //  1000

当我在现实场景中有大量的实际比特时,这似乎是不切实际的,有什么更好的方法可以使用蒙版来检查是否满足 Karen 的条件?

我可以想象向左移动然后向后移动然后向右移动然后向后移动到清晰的位置而不是我感兴趣的位置,但是这似乎过于复杂了。

175642 次浏览
if ( ( param & karen ) == karen )
{
// Do stuff
}

The bitwise 'and' will mask out everything except the bit that "represents" Karen. As long as each person is represented by a single bit position, you could check multiple people with a simple:

if ( ( param & karen ) == karen )
{
// Do Karen's stuff
}
if ( ( param & bob ) == bob )
// Do Bob's stuff
}

要组合位掩码,请使用按位 -或者。在这种简单的情况下,组合的每个值都只有1个位(就像您的示例一样) ,这等同于添加它们。然而,如果你有重叠的位,或者‘ ing 他们处理的情况下优雅。

要解码位掩码,你用掩码 还有你的值,像这样:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();

传统的方法是在 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 指出的那样,上述 IsSet方法已经存在于。NET 4.0: Enum.HasFlag.但是,SetUnset方法似乎没有等价物; 所以我仍然认为这个类有一些优点。


注意: 使用枚举只是解决这个问题的 conventional方法。您完全可以将上述所有代码转换为使用 int,这样也可以很好地工作。

另一个使用位掩码而不是单个 bool 的好理由是作为一个 web 开发人员,当将一个网站集成到另一个网站时,我们经常需要在 query 字符串中发送参数或标志。只要所有的标志都是二进制的,那么使用单个值作为位掩码比将多个值作为 bool 发送要简单得多。我知道还有其他方法可以发送数据(GET、 POST 等) ,但查询字符串上的一个简单参数在大多数情况下对于不敏感的项目已经足够了。尝试在查询字符串上发送128个 bool 值,以便与外部站点进行通信。这还增加了浏览器中不会超出 URL 查询字符串限制的能力

我在这里包含了一个示例,它演示了如何在数据库列中以 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;

简单方法:

[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 */}