C#でのビットマスクの使用


97

次のものがあるとしましょう

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

実際のシナリオで実際のビット数が多い場合、これは非現実的です。マスクを使用して、カレンだけの条件を満たすかどうかを確認するためのより良い方法は何ですか?

左にシフトしてから、右にシフトしてから、関心のあるビット以外のビットをクリアすることも考えられますが、これも複雑すぎるようです。


7
使用法についてコメントしなければなりませんでした。ビット演算を実行する場合は、ビット操作演算子のみを使用してください。つまり、(8 + 2)ではなく、(8 | 2)と考えてください。
ジェフメルカド2010

カレンはまた、すぐにマネージャーに話したいと思います。
Krythic、

回答:


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.HasFlagSetおよびUnset方法が、同等物を持っているように見えません。ですから、このクラスにはいくつかのメリットがあると私は言います。


注:enumの使用は、この問題に取り組むための従来の方法にすぎません。上記のすべてのコードを完全に変換して、代わりにintを使用することもできます。


14
+1は、実際に機能する最初のコードです。また(names & Names.Susan) == Names.Susan、を必要としないを行うこともできNoneます。
Matthew Flaschen、2010

1
@マシュー:ええ、良い点。私Noneはすべての列挙型の値を常に定義するという習慣を身につけているだけだと思います。多くのシナリオで便利になることがわかったからです。
Dan Tao

30
これは組み込みで、ヘルパーメソッドは必要ありません...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@Porges:わあ、どうしてそれを逃したかわからない...それを指摘してくれてありがとう!(ただし、.NET 4.0以降でのみ使用できるようですが、このSetメソッドに相当するものはありません。そのため、ヘルパーメソッドは少なくとも完全に無意味ではありません。)
Dan Tao

6
を使用することnames.HasFlag(Names.Susan)は、(names & Names.Susan) == Names.Susan常にとは限らないことに注意してください(names & Names.Susan) != Names.None。たとえば、次のことを確認するかどうnames.HasFlag(Names.none)names.HasFlag(Names.Susan|Names.Karen)
ABCade

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

ビット単位の「and」は、カレンを「表す」ビット以外のすべてをマスクします。各人が単一のビット位置で表されている限り、シンプルな方法で複数の人をチェックできます。

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)などを行ったので、ビット位置2進数から10進数への変換に慣れていない人にとっては、もう少し明白です。マスクされたビットをシフトすることによってブール結果に変わるので、ブリンディーも良いです。
アナログ放火犯2017年

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

個別のブール値に対してビットマスクを使用するもう1つの本当に良い理由は、Web開発者としてです。あるWebサイトを別のWebサイトに統合するとき、クエリ文字列でパラメーターまたはフラグを送信する必要があることがよくあります。すべてのフラグがバイナリである限り、複数の値をブール値として送信するよりも、単一の値をビットマスクとして使用する方がはるかに簡単です。他にデータを送信する方法(GET、POSTなど)があることは知っていますが、ほとんどの場合、クエリ文字列の単純なパラメーターで、機密性の低いアイテムに十分です。クエリ文字列で128のブール値を送信して、外部サイトと通信してみてください。これにより、ブラウザーでのURLクエリ文字列の制限を超えないようにする機能も追加されます。


OPの質問に対する答えではなく、コメントでした。
sonyisda1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.