Warum haben Enum-Berechtigungen häufig 0, 1, 2, 4 Werte?

159

Warum verwenden Menschen immer Enum-Werte wie 0, 1, 2, 4, 8und nicht 0, 1, 2, 3, 4?

Hat dies etwas mit Bitoperationen usw. zu tun?

Ich würde mich sehr über einen kleinen Beispielausschnitt darüber freuen, wie dies richtig verwendet wird :)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}
Pascal
quelle
1
Mögliches Duplikat von Enum als Flagge mit, setzen und verschieben
Henk Holterman
25
Ich bin nicht einverstanden mit der betrogenen Abstimmung.
zzzzBov
Die UNIX-Methode zum Festlegen von Berechtigungen basiert ebenfalls auf derselben Logik.
Rudy
3
@Pascal: Es kann hilfreich sein, über Bitwise OR (und Bitwise AND ) zu lesen , was |(und &) darstellt. Die verschiedenen Antworten setzen voraus, dass Sie damit vertraut sind.
Brian
2
@IAdapter Ich kann sehen, warum du das denkst, da die Antwort auf beide gleich ist, aber ich denke, die Fragen sind unterschiedlich. Bei der anderen Frage wird lediglich nach einem Beispiel oder einer Erklärung des Flags-Attributs in C # gefragt. Diese Frage scheint sich mit dem Konzept der Bit-Flags und den Grundlagen dahinter zu befassen.
Jeremy S

Antworten:

268

Weil sie Zweierkräfte sind und ich das kann:

var permissions = Permissions.Read | Permissions.Write;

Und vielleicht später ...

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

Es ist ein Bitfeld, in dem jedes gesetzte Bit einer Berechtigung entspricht (oder was auch immer der aufgezählte Wert logisch entspricht). Wenn diese als definiert 1, 2, 3, ...wären, könnten Sie auf diese Weise keine bitweisen Operatoren verwenden und aussagekräftige Ergebnisse erzielen. Um tiefer zu tauchen ...

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

Beachten Sie hier ein Muster? Nehmen wir nun mein ursprüngliches Beispiel, dh

var permissions = Permissions.Read | Permissions.Write;

Dann...

permissions == 00000011

Sehen? Sowohl das Readals auch das WriteBit sind gesetzt, und ich kann dies unabhängig überprüfen (Beachten Sie auch, dass das DeleteBit nicht gesetzt ist und dieser Wert daher keine Berechtigung zum Löschen vermittelt).

Es ermöglicht das Speichern mehrerer Flags in einem einzigen Bitfeld.

Ed S.
quelle
2
@ Malcolm: Das tut es; myEnum.IsSet. Ich bin der Meinung, dass dies eine völlig nutzlose Abstraktion ist und nur dazu dient, das Tippen zu reduzieren, aber meh
Ed S.
1
Gute Antwort, aber Sie sollten erwähnen, warum das Flags-Attribut angewendet wird und wann Sie Flags nicht auch auf einige Enums anwenden möchten.
Andy
3
@Andy: Eigentlich macht das FlagsAttribut kaum mehr als "hübsches Drucken". Sie können einen Aufzählungswert unabhängig vom Vorhandensein des Attributs als Flag verwenden.
Ed S.
3
@detly: Denn wenn Anweisungen in C # einen booleschen Ausdruck erfordern. 0ist nicht false; falseist false. Sie könnten jedoch schreiben if((permissions & Permissions.Write) > 0).
Ed S.
2
Anstelle des "kniffligen" (permissions & Permissions.Write) == Permissions.Writekönnen Sie jetztenum.HasFlag()
Louis Kottmann
147

Wenn es aus den anderen Antworten immer noch nicht klar hervorgeht, denken Sie so darüber nach:

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

ist nur eine kürzere Art zu schreiben:

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
} 

Es gibt acht Möglichkeiten, aber Sie können sie als Kombinationen von nur vier Mitgliedern darstellen. Wenn es 16 Möglichkeiten gäbe, könnten Sie sie als Kombinationen von nur fünf Mitgliedern darstellen. Wenn es vier Milliarden Möglichkeiten gäbe, könnten Sie sie als Kombinationen von nur 33 Mitgliedern darstellen! Es ist offensichtlich weitaus besser, nur 33 Mitglieder zu haben, von denen jedes (außer null) eine Zweierpotenz hat, als zu versuchen, vier Milliarden Gegenstände in einer Aufzählung zu benennen.

Eric Lippert
quelle
32
+1 für das mentale Image eines enummit vier Milliarden Mitgliedern. Und der traurige Teil ist, wahrscheinlich hat es jemand da draußen versucht.
Daniel Pryden
23
@ DanielPryden Als täglicher Leser von Daily WTF würde ich es glauben.
flauschige
1
2 ^ 33 = ~ 8,6 Milliarden. Für 4 Milliarden verschiedene Werte benötigen Sie nur 32 Bit.
Ein CVn
5
@ MichaelKjörling einer der 33 ist für die 0 Standard
Ratschenfreak
@ MichaelKjörling: Um fair zu sein, gibt es nur 32 Mitglieder, die Zweierpotenzen sind, da 0 keine Zweierpotenz ist. "33 Mitglieder, jedes eine Zweierpotenz" ist also nicht genau richtig (es sei denn, Sie zählen 2 ** -infinityals Zweierpotenz).
Brian
36

Weil diese Werte eindeutige Bitpositionen in Binärform darstellen:

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

usw., so

1 | 2 == binary 00000011

BEARBEITEN:

3 == binary 00000011

3 in binär wird durch einen Wert von 1 sowohl an der Einsen- als auch an der Zweierstelle dargestellt. Es ist eigentlich das gleiche wie der Wert 1 | 2. Wenn Sie also versuchen, die binären Stellen als Flags zu verwenden, um einen Zustand darzustellen, ist 3 normalerweise nicht aussagekräftig (es sei denn, es gibt einen logischen Wert, der tatsächlich die Kombination der beiden ist).

Zur weiteren Verdeutlichung möchten Sie möglicherweise Ihre Beispielaufzählung wie folgt erweitern:

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

Deshalb in ich habe Permissions.All, habe ich auch implizit Permissions.Read, Permissions.WriteundPermissions.Delete

Chris Shain
quelle
und was ist das problem mit 2 | 3?
Pascal
1
@Pascal: Da 3es 11binär ist, dh nicht einem einzelnen gesetzten Bit zugeordnet ist, verlieren Sie die Fähigkeit, 1 Bit an einer beliebigen Position einem aussagekräftigen Wert zuzuordnen.
Ed S.
8
@Pascal anders ausgedrückt , 2|3 == 1|3 == 1|2 == 3. Also , wenn Sie einen Wert mit binären haben 00000011und Ihre Fahnen enthalten Werte 1, 2und 3, dann würden Sie nicht wissen , ob dieser Wert repräsentiert 1 and 3, 2 and 3, 1 and 2oder only 3. Das macht es viel weniger nützlich.
Yshavit
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
}

Ich denke, so zu schreiben ist leichter zu verstehen und zu lesen, und Sie müssen es nicht berechnen.

Bulldozer
quelle
5

Diese werden verwendet, um Bitflags darzustellen, die Kombinationen von Aufzählungswerten ermöglichen. Ich denke, es ist klarer, wenn Sie die Werte in Hex-Notation schreiben

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
JaredPar
quelle
4
@Pascal: Vielleicht ist es zu diesem Zeitpunkt für Sie besser lesbar, aber wenn Sie Erfahrung sammeln, wird das Anzeigen von Bytes in Hex zur zweiten Natur. Zwei Ziffern in Hex-Zuordnungen zu einem Byte werden zu 8 Bits zugeordnet (naja ... ein Byte ist normalerweise sowieso 8 Bits ... nicht immer wahr, aber für dieses Beispiel ist es in Ordnung, zu verallgemeinern).
Ed S.
5
@Pascal schnell, was bekommst du, wenn du 4194304mit 2 multiplizierst ? Wie wäre es 0x400000? Es ist viel einfacher 0x800000als die richtige Antwort zu erkennen als 8388608und es ist auch weniger fehleranfällig, den Hex-Wert einzugeben.
Phoog
6
Auf einen Blick ist es viel einfacher zu erkennen, ob Ihre Flags richtig gesetzt sind (dh Potenzen von 2 sind), wenn Sie Hex verwenden. Ist 0x10000eine Zweierpotenz? Ja, es beginnt mit 1, 2, 4 oder 8 und hat danach alle Nullen. Sie müssen 0x10 nicht mental in 16 übersetzen (obwohl dies wahrscheinlich irgendwann zur zweiten Natur wird). Stellen Sie sich das einfach als "eine Potenz von 2" vor.
Brian
1
Ich bin absolut mit Jared darüber, dass es viel einfacher ist, in Hex zu notieren. Sie verwenden nur 1 2 4 8 und verschieben
bevacqua
1
Persönlich bevorzuge ich nur die Verwendung von zB P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Ich bin mir nicht sicher, ob diese Art der konstanten Faltung in C # funktioniert, aber in C / C ++ (und Java, glaube ich) funktioniert sie einwandfrei.
flauschige
1

Dies ist eigentlich eher ein Kommentar, aber da dies die Formatierung nicht unterstützen würde, wollte ich nur eine Methode einfügen, die ich zum Einrichten von Flag-Aufzählungen verwendet habe:

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

Ich finde diesen Ansatz besonders hilfreich während der Entwicklung, wenn Sie Ihre Flags in alphabetischer Reihenfolge halten möchten. Wenn Sie feststellen, dass Sie einen neuen Flag-Wert hinzufügen müssen, können Sie ihn einfach alphabetisch einfügen. Der einzige Wert, den Sie ändern müssen, ist der, dem er jetzt vorausgeht.

Beachten Sie jedoch, dass es nach der Veröffentlichung einer Lösung für ein Produktionssystem (insbesondere wenn die Aufzählung ohne enge Kopplung verfügbar ist, z. B. über einen Webdienst) dringend empfohlen wird, einen vorhandenen Wert innerhalb der Aufzählung nicht zu ändern.

Mike Guthrie
quelle
1

Viele gute Antworten auf diese Frage ... Ich sage nur ... wenn Sie nicht mögen oder nicht leicht verstehen können, was die <<Syntax auszudrücken versucht ... Ich persönlich bevorzuge eine Alternative (und wage es zu sagen, einen einfachen Enum-Deklarationsstil). …

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

So viel einfacher (zumindest für mich) zu verstehen. Richten Sie die aus ... beschreiben Sie das gewünschte Ergebnis, erhalten Sie das gewünschte Ergebnis. Keine "Berechnungen" erforderlich.

Alex Gray
quelle