在C#中使用位掩码


97

假设我有以下内容

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

我将10(8 + 2)作为参数传递给方法,我想对此进行解码以表示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

当我在现实世界中有大量实际位时,这似乎不切实际,有什么更好的方法使用遮罩来检查我是否满足卡伦的条件?

我可以想到先左移然后再右移然后再右移以清除除我感兴趣的位以外的其他位,但这似乎过于复杂。


7
只需评论使用情况。如果要执行位操作,则应仅使用位操作运算符。即,将其视为(8 | 2),而不是(8 + 2)。
杰夫·梅卡多

凯伦(Karen)也想立即与您的经理通话。
克里斯蒂克

Answers:


198

传统方法是在上使用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所指出的,IsSet.NET 4.0中已经存在上述方法的等效项:Enum.HasFlag。不过,SetUnset方法似乎没有等效项。所以我还是要说这堂课有一些优点。


注意:使用枚举只是解决此问题的常规方法。您可以完全翻译上面的所有代码以改为使用int,它也将正常工作。


14
+1是第一个实际起作用的代码。您也可以这样做(names & Names.Susan) == Names.Susan,不需要安装None
马修·弗拉申

1
@Matthew:哦,是的,很好。我想我只是养成始终None为所有枚举定义值的习惯,因为我发现它最终在许多情况下都很方便。
丹涛

30
这是内置的,您不需要辅助方法...var susanIsIncluded = names.HasFlag(Names.Susan);
porges 2010年

2
@Porges:哇,不知道我是怎么想念的……谢谢你指出这一点!(尽管它似乎仅从.NET 4.0开始可用,但是...该Set方法也没有等效方法。因此,我想说辅助方法至少并非完全没有用。)
Dan Tao

6
请注意,using names.HasFlag(Names.Susan)是like (names & Names.Susan) == Names.Susan,但并不总是like (names & Names.Susan) != Names.None。例如,如果你去检查一下names.HasFlag(Names.none)names.HasFlag(Names.Susan|Names.Karen)
ABCade

20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

按位“与”将掩盖除“代表”凯伦的位以外的所有内容。只要每个人都用一个位表示,就可以用简单的方法检查多个人:

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

12

我在此处提供了一个示例,该示例演示如何将掩码以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;

我做了类似的事情,但是在定义蒙版时我做了Mon = Math.Power(2,0),Tues = Math.Pow(2,1),Wed = Math.Pow(2,2)等,所以位的位置对于那些不习惯将二进制转换为十进制的用户来说,这是显而易见的。布林迪的也很好,因为它通过移位被屏蔽的位变成布尔结果。
模拟纵火犯

7

要组合位掩码,您想使用按位。在琐碎的情况下,您组合的每个值都恰好有1位(例如您的示例),这等同于将它们相加。如果您有重叠的位,或者对其进行“处理”,则可以很好地处理此情况。

要使用掩码对位掩码您的值进行解码,如下所示:

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

1
在C#中,不能使用整数作为布尔值。
影子”

6

简单的方法:

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

似乎上面已经提供了所有这些信息。如果您要提供任何信息,则应清楚标记。
sonyisda1

1
其他答案中未提供使用HasFlag()和的简单示例[Flags]
A-Sharabiani

0

使用位掩码而不是单独的布尔符号的另一个很好的理由是作为Web开发人员,将一个网站集成到另一个网站时,我们经常需要在查询字符串中发送参数或标志。只要您所有的标志都是二进制的,使用单个值作为位掩码就比将多个值作为布尔发送要容易得多。我知道还有其他发送数据的方法(GET,POST等),但是查询字符串上的一个简单参数在大多数情况下对于不敏感的项目就足够了。尝试在查询字符串上发送128个布尔值以与外部站点进行通信。这也提供了不对浏览器中的url查询字符串施加限制的附加功能


对OP的问题并不是真正的答案-应该是评论。
sonyisda1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.