web-dev-qa-db-ja.com

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

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

私が興味のあるビット以外のビットをクリアするために、左にシフトし、次に右にシフトし、次に右にシフトすることを考えることができますが、これも非常に複雑に見えます。

87
Matt

これを行う従来の方法は、Flagsenum属性を使用することです。

[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 。ただし、SetメソッドとUnsetメソッドには同等のものはありません。このクラスにはまだメリットがあると思います。


注:列挙型の使用は、この問題に取り組むための単なる従来型の方法です。代わりにintを使用するように上記のすべてのコードを完全に翻訳できますが、同様に機能します。

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

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

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

ここに、マスクをデータベース列に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;
11
Nick Wright

ビットマスクを結合するには、ビット単位-またはを使用します。組み合わせるすべての値がちょうど1ビットであるというごく単純なケースでは(例のように)、それらを追加するのと同等です。ただし、ビットが重複している場合は、or 'ingでケースを適切に処理します。

ビットマスクをデコードするには、次のようにマスクを使用して値をandにします。

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

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

0
Greg Osborne

簡単な方法:

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