简体   繁体   English

在C#中使用位掩码

[英]Using a bitmask in C#

Let's say I have the following 假设我有以下内容

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

and I pass 10 (8 + 2) as a parameter to a method and I want to decode this to mean susan and karen 我将10(8 + 2)作为参数传递给方法,我想将其解码为苏珊和卡伦

I know that 10 is 1010 我知道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

Right now all i can think of is to check whether the number i passed is 现在,我能想到的是检查我通过的号码是否正确

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

When I have a larger number of actual bits in my real world scenario, this seems impractical, what is a better way using a mask to just check whether or not I meet the condition for just karen? 当我在我的真实场景中拥有更多的实际比特时,这似乎是不切实际的,使用掩码来检查我是否满足karen条件的更好方法是什么?

I can think of shifting left then back then shifting right then back to clear bits other than the one I'm interested in, but this also seems overly complex. 我可以想到向左移动然后向后移动然后向右移动然后向后移动到除了我感兴趣的那个之外的其他位置,但这似乎也过于复杂。

The traditional way to do this is to use the Flags attribute on an enum : 传统的方法是在enum上使用Flags属性:

[Flags]
public enum Names
{
    None = 0,
    Susan = 1,
    Bob = 2,
    Karen = 4
}

Then you'd check for a particular name as follows: 然后,您将检查特定名称,如下所示:

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;

Logical bitwise combinations can be tough to remember, so I make life easier on myself with a FlagsHelper class*: 逻辑按位组合很难记住,因此我使用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));
    }
}

This would allow me to rewrite the above code as: 这将允许我将上面的代码重写为:

Names names = Names.Susan | Names.Bob;

bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);

bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);

Note I could also add Karen to the set by doing this: 注意我也可以通过这样做将Karen添加到集合中:

FlagsHelper.Set(ref names, Names.Karen);

And I could remove Susan in a similar way: 我可以用类似的方式删除Susan

FlagsHelper.Unset(ref names, Names.Susan);

*As Porges pointed out, an equivalent of the IsSet method above already exists in .NET 4.0: Enum.HasFlag . *正如Porges所指出的,.NET 4.0中已存在等效的IsSet方法: Enum.HasFlag The Set and Unset methods don't appear to have equivalents, though; 但是, SetUnset方法似乎没有等价物; so I'd still say this class has some merit. 所以我仍然说这堂课有一些优点。


Note: Using enums is just the conventional way of tackling this problem. 注意:使用枚举只是解决此问题的传统方法。 You can totally translate all of the above code to use ints instead and it'll work just as well. 您可以完全翻译所有上述代码以使用ints,它也可以正常工作。

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

The bitwise 'and' will mask out everything except the bit that "represents" Karen. 按位'和'将屏蔽除“代表”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
}

I have included an example here which demonstrates how you might store the mask in a database column as an int, and how you would reinstate the mask later on: 我在这里包含了一个示例,演示了如何将掩码作为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;

To combine bitmasks you want to use bitwise- or . 要组合您想要按位使用的位掩码。 In the trivial case where every value you combine has exactly 1 bit on (like your example), it's equivalent to adding them. 在简单的情况下,你组合的每个值都只有1位(就像你的例子),它相当于添加它们。 If you have overlapping bits however, or 'ing them handles the case gracefully. 但是,如果你有重叠位, 或者他们优雅地处理案例。

To decode the bitmasks you and your value with a mask, like so: 要使用掩码解码您您的值的位掩码,如下所示:

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

One other really good reason to use a bitmask vs individual bools is as a web developer, when integrating one website to another, we frequently need to send parameters or flags in the querystring. 使用位掩码与单个bools的另一个非常好的理由是作为Web开发人员,当将一个网站集成到另一个网站时,我们经常需要在查询字符串中发送参数或标志。 As long as all of your flags are binary, it makes it much simpler to use a single value as a bitmask than send multiple values as bools. 只要所有标志都是二进制,就可以使用单个值作为位掩码,而不是将多个值作为bools发送。 I know there are otherways to send data (GET, POST, etc.), but a simple parameter on the querystring is most of the time sufficient for nonsensitive items. 我知道还有其他方法可以发送数据(GET,POST等),但查询字符串上的一个简单参数大部分时间都足以用于非敏感项。 Try to send 128 bool values on a querystring to communicate with an external site. 尝试在查询字符串上发送128个bool值以与外部站点进行通信。 This also gives the added ability of not pushing the limit on url querystrings in browsers 这也增加了在浏览器中不限制url查询字符串的能力

Easy Way: 简单的方法:

[Flags]
public enum MyFlags {
    None = 0,
    Susan = 1,
    Alice = 2,
    Bob = 4,
    Eve = 8
}

To set the flags use logical "or" operator | 要设置标志,请使用逻辑“或”运算符| :

MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;

And to check if a flag is included use HasFlag : 并检查是否包含标志使用HasFlag

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM