列挙型のアクセス許可に0、1、2、4の値が含まれていることが多いのはなぜですか?


159

なぜ人々はいつものように列挙値を使用していません0, 1, 2, 4, 80, 1, 2, 3, 4

これはビット操作などと関係がありますか?

これが正しく使用される方法に関する小さなサンプルスニペットを本当に感謝します:)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}


25
私は重複投票に同意しません。
zzzzBov 2012年

パーミッションを設定するUNIXの方法も同じロジックに基づいています。
Rudy

3
@Pascal:ビットワイズOR(およびビットワイズAND)について読むと役立つかもしれません。これは|(および&)が表すものです。さまざまな答えは、あなたがそれに精通していることを前提としています。
ブライアン

2
@IAdapter両方の答えは同じなので、なぜそう思うのかはわかりますが、質問は違うと思います。もう1つの質問は、C#のFlags属性の例または説明を要求するだけです。この質問は、ビットフラグの概念と、その背後にある基礎についてのようです。
ジェレミーS

回答:


268

彼らは2の累乗であり、私はこれを行うことができるので:

var permissions = Permissions.Read | Permissions.Write;

そしておそらく後で...

if( (permissions & Permissions.Write) == Permissions.Write )
{
    // we have write access
}

これはビット・フィールドであり、設定された各ビットは何らかの許可(または列挙値が論理的に対応するものは何でも)に対応します。これらが定義されている1, 2, 3, ...場合、この方法でビット演算子を使用して意味のある結果を得ることができません。より深く掘り下げるには...

Permissions.Read   == 1 == 00000001
Permissions.Write  == 2 == 00000010
Permissions.Delete == 4 == 00000100

ここにパターンに気づきましたか?ここで、元の例、つまり

var permissions = Permissions.Read | Permissions.Write;

その後...

permissions == 00000011

見る?両方ReadWriteビットが設定され、そして私は、独立して確認することができる(ようにしても通知Deleteビットがされていない設定を、したがって、この値は削除する権限を伝達しません)。

これにより、ビットの単一フィールドに複数のフラグを格納できます。


2
@マルコム:そうです。myEnum.IsSet。これは完全に役に立たない抽象化であり、タイピングを削減するためにのみ役立つと私は思うが、まあ
Ed S.

1
良い答えですが、Flags属性が適用される理由と、一部の列挙型にFlagsを適用したくない場合についても言及する必要があります。
アンディ

3
@Andy:実際、このFlags属性は「きれいな印刷」を提供するだけです。属性の存在に関係なく、列挙値をフラグとして使用できます。
Ed S.

3
@detly:C#のifステートメントにはブール式が必要だからです。 0ないfalse; falseですfalse。しかし、あなたは書くことができますif((permissions & Permissions.Write) > 0)
Ed S.

2
「トリッキー」の代わりに、(permissions & Permissions.Write) == Permissions.Write現在使用できますenum.HasFlag()
Louis Kottmann

147

それでも他の回答から明確でない場合は、次のように考えてください。

[Flags] 
public enum Permissions 
{   
   None = 0,   
   Read = 1,     
   Write = 2,   
   Delete = 4 
} 

書くためのより短い方法です:

public enum Permissions 
{   
    DeleteNoWriteNoReadNo = 0,   // None
    DeleteNoWriteNoReadYes = 1,  // Read
    DeleteNoWriteYesReadNo = 2,  // Write
    DeleteNoWriteYesReadYes = 3, // Read + Write
    DeleteYesWriteNoReadNo = 4,   // Delete
    DeleteYesWriteNoReadYes = 5,  // Read + Delete
    DeleteYesWriteYesReadNo = 6,  // Write + Delete
    DeleteYesWriteYesReadYes = 7, // Read + Write + Delete
} 

8つの可能性がありますが、4つのメンバーのみの組み合わせとして表すことができます。16の可能性がある場合、それらを5つのメンバーのみの組み合わせとして表すことができます。40億の可能性がある場合は、33のメンバーのみの組み合わせとして表すことができます。列挙型で40億個のアイテムに名前を付けるよりも、33のメンバー(それぞれ0を除く)を2の累乗にするほうがはるかに優れています。


32
enumメンバーが40億人いるメンタルイメージの場合は+1 。そして悲しい部分は、おそらく誰かがそれを試したことがあるでしょう。
Daniel Pryden

23
@DanielPryden Daily WTFの毎日の読者として、私はそれを信じています。
ふわふわした

1
2 ^ 33 =〜86億。40億の異なる値の場合、必要なのは32ビットだけです。
CVn 2012年

5
@MichaelKjörling33の1つは0のデフォルトです
ラチェットフリーク

@MichaelKjörling:公平を期すために、0は2の累乗ではないため、2の累乗である32のメンバーしかありません。したがって、「33のメンバー、それぞれが2の累乗」は正確には正しくありません(2 ** -infinity2の累乗として数えない限り)。
ブライアン

36

これらの値はバイナリの一意のビット位置を表すため、次のようになります。

1 == binary 00000001
2 == binary 00000010
4 == binary 00000100

など

1 | 2 == binary 00000011

編集:

3 == binary 00000011

2進数の3は、1の場所と2の場所の両方で値1によって表されます。実際には値と同じです1 | 2。したがって、バイナリの場所をフラグとして使用して状態を表す場合、3は通常意味がありません(実際には2つの組み合わせである論理値がない限り)。

さらに明確にするために、例の列挙型を次のように拡張することもできます。

[Flags]
public Enum Permissions
{
  None = 0,   // Binary 0000000
  Read = 1,   // Binary 0000001
  Write = 2,  // Binary 0000010
  Delete = 4, // Binary 0000100
  All = 7,    // Binary 0000111
}

したがって、私が持っている中でPermissions.All、私はまた、暗黙のうちに持っているPermissions.ReadPermissions.Writeと、Permissions.Delete


2 | 3の問題は何ですか?
Pascal

1
@Pascal:311バイナリであるため、つまり単一のセットビットにマップされないため、任意の位置の1ビットを意味のある値にマップする機能を失います。
Ed S.

8
@Pascalは、別の方法を置きます2|3 == 1|3 == 1|2 == 3。あなたがバイナリで値を持っているのであれば00000011、そしてあなたのフラグが値を含め12、及び3、その後、あなたはその値が表す場合は知っているだろう1 and 32 and 31 and 2またはonly 3。そのため、あまり役に立たなくなります。
yshavit 2012年

10
[Flags]
public Enum Permissions
{
    None   =    0; //0000000
    Read   =    1; //0000001
    Write  = 1<<1; //0000010
    Delete = 1<<2; //0000100
    Blah1  = 1<<3; //0001000
    Blah2  = 1<<4; //0010000
}

このように書く方が理解しやすく、読みやすく、計算する必要がないと思います。


5

これらは、列挙値の組み合わせを可能にするビットフラグを表すために使用されます。値を16進表記で記述した方がわかりやすいと思います

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}

4
@Pascal:おそらく、この時点では読みやすくなりますが、経験を積むにつれて、16進数でバイトを表示することが第2の性質になります。16進数の2桁が1バイトにマップされ、8ビットにマップされます(まあ...とにかくバイトは通常8ビットです...常にtrueとは限りませんが、この例では一般化しても問題ありません)。
Ed S.

5
@Pascalすばやく、41943042 を掛けると何が得られますか?いかが0x400000ですか?0x800000は正解として認識する方がはるかに簡単8388608です。また、16進値を入力することでエラーが発生しにくくなります。
phoog 2012年

6
16進数を使用すると、フラグが適切に設定されている(つまり、2の累乗である)と、一目でわかりやすくなります。である0x100002のべき乗では?はい、1、2、4、または8で始まり、その後すべて0になります。精神的に0x10を16に変換する必要はありません(そうすることで、最終的には2番目の性質になるでしょう)、それを「2のべき乗」と考えてください。
Brian

1
16進数で表記する方がはるかに簡単だということについて、Jaredと完全に一致しています。1 2 4 8を使用してシフト
bevacqua 2012年

1
個人的には、たとえばP_READ = 1 << 0、P_WRITE = 1 <、1、P_RW = P_READ | P_WRITEを使用することを好みます。このような定数の折りたたみがC#で機能するかどうかはわかりませんが、C / C ++(Javaと同様)では問題なく機能します。
ふわふわ

1

これは実際にはもっとコメントですが、それはフォーマットをサポートしないので、フラグ列挙を設定するために採用したメソッドを含めたかっただけです:

[Flags]
public enum FlagTest
{
    None = 0,
    Read = 1,
    Write = Read * 2,
    Delete = Write * 2,
    ReadWrite = Read|Write
}

このアプローチは、フラグをアルファベット順に維持したい場合に、開発中に特に役立ちます。新しいフラグ値を追加する必要があると判断した場合は、それをアルファベット順に挿入するだけでよく、変更する必要がある唯一の値は、その前の値です。

ただし、ソリューションがプロダクションシステムに公開されると(特に、列挙がWebサービスなどの密結合なしに公開される場合)、列挙内の既存の値を変更しないことを強くお勧めします。


1

これに対する良い答えはたくさんあります...言いたいことは..気に入らない場合、または構文が表現しようとしていることを簡単に理解できない場合<<..私は個人的に代替案を好みます(そして、あえて言うと、単純な列挙型宣言スタイル) …

typedef NS_OPTIONS(NSUInteger, Align) {
    AlignLeft         = 00000001,
    AlignRight        = 00000010,
    AlignTop          = 00000100,
    AlignBottom       = 00001000,
    AlignTopLeft      = 00000101,
    AlignTopRight     = 00000110,
    AlignBottomLeft   = 00001001,
    AlignBottomRight  = 00001010
};

NSLog(@"%ld == %ld", AlignLeft | AlignBottom, AlignBottomLeft);

LOG 513 == 513

理解するのがとても簡単になりました(少なくとも私にとっては)。必要な結果を記述し、必要な結果を取得してください。「計算」は必要ありません。

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