Was bedeutet das Enum-Attribut [Flags] in C #?

1446

Von Zeit zu Zeit sehe ich eine Aufzählung wie die folgende:

[Flags]
public enum Options 
{
    None    = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}

Ich verstehe nicht genau, was das [Flags]Attribut bewirkt.

Hat jemand eine gute Erklärung oder ein Beispiel, das er posten könnte?

Brian Leahy
quelle
Es ist auch erwähnenswert,, zusätzlich zu der akzeptierten Antwort, dass VB.NET tatsächlich erfordert [Flags] - zumindest nach den .NET - Typen: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/...
Rushyo
8
Hinweis, heutzutage in VB nicht erforderlich. Verhalten als C # speichern - ändert nur die Ausgabe von ToString (). Beachten Sie, dass Sie auch ein logisches ODER innerhalb der Aufzählung selbst ausführen können. Sehr cool. Katze = 1, Hund = 2, CatAndDog = Katze || Hund.
Chalky
14
@Chalky Du meinst CatAndDog = Cat | Dog(das logische ODER anstelle des Bedingten), nehme ich an?
DdW
5
@DdW, teilweise richtig: | sollte verwendet werden, aber | heißt das binäre ODER. II ist das logische ODER (das einen Kurzschluss zulässt): Zumindest laut Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

Antworten:

2153

Das [Flags]Attribut sollte immer dann verwendet werden, wenn die Aufzählung eine Sammlung möglicher Werte anstelle eines einzelnen Werts darstellt. Solche Sammlungen werden häufig mit bitweisen Operatoren verwendet, zum Beispiel:

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Beachten Sie, dass das [Flags]Attribut dies nicht selbst aktiviert - alles, was es tut, ist eine schöne Darstellung durch die .ToString()Methode:

enum Suits { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }
[Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }

...

var str1 = (Suits.Spades | Suits.Diamonds).ToString();
           // "5"
var str2 = (SuitsFlags.Spades | SuitsFlags.Diamonds).ToString();
           // "Spades, Diamonds"

Es ist auch wichtig zu beachten, dass die Aufzählungswerte [Flags] nicht automatisch zu Zweierpotenzen werden. Wenn Sie die numerischen Werte weglassen, funktioniert die Aufzählung nicht wie bei bitweisen Operationen zu erwarten, da die Werte standardmäßig mit 0 beginnen und inkrementieren.

Falsche Erklärung:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}

Wenn die Werte auf diese Weise deklariert werden, sind sie Gelb = 0, Grün = 1, Rot = 2, Blau = 3. Dies macht sie als Flags unbrauchbar.

Hier ist ein Beispiel für eine korrekte Deklaration:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

Um die unterschiedlichen Werte in Ihrer Eigenschaft abzurufen, haben Sie folgende Möglichkeiten:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow is allowed...
}

oder vor .NET 4:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow is allowed...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green is allowed...
}    

Unter der Decke

Dies funktioniert, weil Sie in Ihrer Aufzählung Zweierpotenzen verwendet haben. Unter der Decke sehen Ihre Aufzählungswerte in binären Einsen und Nullen folgendermaßen aus:

 Yellow: 00000001
 Green:  00000010
 Red:    00000100
 Blue:   00001000

Nachdem Sie Ihre Eigenschaft AllowedColors mit dem binären bitweisen ODER- |Operator auf Rot, Grün und Blau festgelegt haben, sieht AllowedColors folgendermaßen aus:

myProperties.AllowedColors: 00001110

Wenn Sie also den Wert abrufen, führen Sie tatsächlich bitweise UND &für die Werte aus:

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Hey, this is the same as MyColor.Green!

Der Wert None = 0

Und in Bezug auf die Verwendung von 0in Ihrer Aufzählung, zitiert aus MSDN:

[Flags]
public enum MyColors
{
    None = 0,
    ....
}

Verwenden Sie None als Namen der Flag-Aufzählungskonstante, deren Wert Null ist. Sie können die Konstante Keine nicht in einer bitweisen UND-Operation verwenden, um nach einem Flag zu testen, da das Ergebnis immer Null ist. Sie können jedoch einen logischen und keinen bitweisen Vergleich zwischen dem numerischen Wert und der Aufzählungskonstante None durchführen, um festzustellen, ob Bits im numerischen Wert gesetzt sind.

Weitere Informationen zum Flags-Attribut und seiner Verwendung finden Sie unter msdn und zum Entwerfen von Flags unter msdn

andnil
quelle
156
Flags selbst macht nichts. Außerdem benötigt C # keine Flags an sich. Aber die ToStringUmsetzung Ihrer ENUM nutzt Flags, und so funktioniert Enum.IsDefined, Enum.Parseusw. Versuchen Flaggen und Blick auf das Ergebnis von MyColor.Yellow entfernen | MyColor.Red; ohne es bekommst du "5", mit Flags bekommst du "Yellow, Red". Einige andere Teile des Frameworks verwenden ebenfalls [Flags] (z. B. XML-Serialisierung).
Ruben
7
// Gelb wurde gesetzt ... finde ich etwas irreführend. Es wurde nichts festgelegt. Dies bedeutet, dass Gelb Mitglied Ihrer AllowedColors ist. Vielleicht ist es besser, // Gelb ist zulässig.
Scott Weaver
48
Ich bevorzuge Konstanten der Form A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Viel einfacher zu lesen, zu verstehen, visuell zu überprüfen und zu ändern.
Nick Westgate
2
@borrrden, Hölle yeahh! Ich habe Folgendes gefunden: msdn.microsoft.com/en-us/library/system.enum.aspx - siehe Teil "Anmerkungen": "Enum ist die Basisklasse für alle Aufzählungen in .NET Framework." und "Die Aufzählung erbt nicht explizit von Enum; die Vererbungsbeziehung wird implizit vom Compiler behandelt." Wenn Sie also schreiben: public enum bla bla bla - dies ist ein Werttyp. Aber die HasFlag-Methode möchte, dass Sie ihm eine Instanz von System.Enum geben, die eine Klasse ist (Referenztyp :)
Aleksei Chepovoi
2
Wenn Sie ein Flag von der Aufzählung ausschließen möchten, verwenden Sie xor, das in C # ^ ist. Also wenn du hast myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Sie können tun:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe
779

Sie können dies auch tun

[Flags]
public enum MyEnum
{
    None   = 0,
    First  = 1 << 0,
    Second = 1 << 1,
    Third  = 1 << 2,
    Fourth = 1 << 3
}

Ich finde das Bit-Shifting einfacher als das Eingeben von 4,8,16,32 und so weiter. Dies hat keine Auswirkungen auf Ihren Code, da alles zur Kompilierungszeit ausgeführt wird

Orion Edwards
quelle
18
Das ist es, was ich meine, ich bevorzuge es, das vollständige int im Quellcode zu haben. Wenn ich eine Spalte in einer Tabelle in der Datenbank mit dem Namen MyEnum habe, in der ein Wert einer der Aufzählungen gespeichert ist, und ein Datensatz 131.072 enthält, muss ich meinen Taschenrechner herausholen, um herauszufinden, dass dies der Aufzählung mit dem Wert 1 entspricht << 17. Im Gegensatz dazu, nur den Wert 131.072 in der Quelle zu sehen.
JeremyWeir
6
@jwg Ich stimme zu, es ist albern, übermäßig besorgt über die Laufzeitleistung zu sein, aber trotzdem finde ich es schön zu wissen, dass dies keine Bitverschiebungen einfügen wird, wo immer Sie die Aufzählung verwenden. Eher eine "das ist ordentlich" Sache als alles, was mit Leistung zu tun hat
Orion Edwards
18
@JeremyWeir - In einem Flag-Aufzählungswert werden mehrere Bits gesetzt. Ihre Methode der Datenanalyse ist also unangemessen. Führen Sie eine Prozedur aus, um Ihren ganzzahligen Wert binär darzustellen. 131.072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32]. Wenn das 17. Bit gesetzt ist, kann ein Enum-Wert von 1 << 17 leicht bestimmt werden. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. Die Aufzählungswerte weisen 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. usw. usw. zu, ohne dass auch nur die Hilfe eines Taschenrechners überhaupt .. was ich bezweifle, dass Sie überhaupt bestimmen könnten, ohne die binäre Wiederholung zu bekommen.
Brett Caswell
13
Außerdem wird darauf hingewiesen, dass Sie eine Bitverschiebung von "vorherigen" Enum-Werten anstatt direkt von Zahlen Third = Second << 1Third = 1 << 2
vornehmen
26
Ich mag diese, aber wenn man in den oberen Grenzen des zugrunde liegenden Aufzählungstypen erhalten, die Compiler warnen nicht vor Bit - Verschiebungen wie 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2, und so weiter. Wenn Sie dagegen ThirtySecond = 2147483648für eine Aufzählung vom Typ int sagen , gibt der Compiler einen Fehler aus.
aaaantoine
115

Wenn Sie die Antworten https://stackoverflow.com/a/8462/1037948 (Deklaration über Bitverschiebung) und https://stackoverflow.com/a/9117/1037948 (Verwendung von Kombinationen in der Deklaration) kombinieren, können Sie frühere Werte eher bitverschieben als mit Zahlen. Nicht unbedingt zu empfehlen, aber nur darauf hinzuweisen, dass Sie es können.

Eher, als:

[Flags]
public enum Options : byte
{
    None    = 0,
    One     = 1 << 0,   // 1
    Two     = 1 << 1,   // 2
    Three   = 1 << 2,   // 4
    Four    = 1 << 3,   // 8

    // combinations
    OneAndTwo = One | Two,
    OneTwoAndThree = One | Two | Three,
}

Sie können deklarieren

[Flags]
public enum Options : byte
{
    None    = 0,
    One     = 1 << 0,       // 1
    // now that value 1 is available, start shifting from there
    Two     = One << 1,     // 2
    Three   = Two << 1,     // 4
    Four    = Three << 1,   // 8

    // same combinations
    OneAndTwo = One | Two,
    OneTwoAndThree = One | Two | Three,
}

Bestätigung mit LinqPad:

foreach(var e in Enum.GetValues(typeof(Options))) {
    string.Format("{0} = {1}", e.ToString(), (byte)e).Dump();
}

Ergebnisse in:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
drzaus
quelle
23
Die Kombinationen sind eine gute Empfehlung, aber ich denke, die verkettete Bitverschiebung ist anfällig für Fehler beim Kopieren und Einfügen wie Zwei = Eins << 1, Drei = Eins << 1 usw. Die inkrementierenden ganzen Zahlen der Form 1 << n sind sicherer und die Absicht ist klarer.
Rupert Rawnsley
2
@ RupertRawnsley, um meine Antwort zu zitieren:> Nicht unbedingt empfehlen, aber nur darauf hinweisen, dass Sie können
drzaus
49

Im Folgenden finden Sie ein Beispiel, das die Deklaration und die mögliche Verwendung zeigt:

namespace Flags
{
    class Program
    {
        [Flags]
        public enum MyFlags : short
        {
            Foo = 0x1,
            Bar = 0x2,
            Baz = 0x4
        }

        static void Main(string[] args)
        {
            MyFlags fooBar = MyFlags.Foo | MyFlags.Bar;

            if ((fooBar & MyFlags.Foo) == MyFlags.Foo)
            {
                Console.WriteLine("Item has Foo flag set");
            }
        }
    }
}
ABl.
quelle
Dieses Beispiel funktioniert auch, wenn Sie [Flags] weglassen. Es ist der [Flags] Teil, über den ich lernen möchte.
Gbarry
39

In Erweiterung zur akzeptierten Antwort können in C # 7 die Enum-Flags mit binären Literalen geschrieben werden:

[Flags]
public enum MyColors
{
    None   = 0b0000,
    Yellow = 0b0001,
    Green  = 0b0010,
    Red    = 0b0100,
    Blue   = 0b1000
}

Ich denke, diese Darstellung macht deutlich, wie die Flaggen unter der Decke funktionieren .

Thorkil Holm-Jacobsen
quelle
2
Und in C # 7.2 ist es mit dem führenden Trennzeichen noch deutlicher ! 0b_0100
Vincenzo Petronio
37

Ich habe kürzlich nach etwas Ähnlichem gefragt .

Wenn Sie Flags verwenden, können Sie Enums eine Erweiterungsmethode hinzufügen, um das Überprüfen der enthaltenen Flags zu vereinfachen (siehe Beitrag für Details).

Dies ermöglicht Ihnen Folgendes:

[Flags]
public enum PossibleOptions : byte
{
    None = 0,
    OptionOne = 1,
    OptionTwo = 2,
    OptionThree = 4,
    OptionFour = 8,

    //combinations can be in the enum too
    OptionOneAndTwo = OptionOne | OptionTwo,
    OptionOneTwoAndThree = OptionOne | OptionTwo | OptionThree,
    ...
}

Dann können Sie tun:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

if( opt.IsSet( PossibleOptions.OptionOne ) ) {
    //optionOne is one of those set
}

Ich finde das leichter zu lesen als die meisten Möglichkeiten, die enthaltenen Flags zu überprüfen.

Keith
quelle
IsSet ist eine Erweiterungsmethode, die ich annehme?
Robert MacLean
Ja - lesen Sie die andere Frage, auf die ich für Details verweise
Keith
71
.NET 4 fügt eine HasFlagAufzählung zu Aufzählungen hinzu, so dass Sie opt.HasFlag( PossibleOptions.OptionOne )ohne Ihre eigenen Erweiterungen schreiben müssen
Orion Edwards
1
Beachten Sie, dass HasFlagist viel langsamer als tun bitweise Operationen.
Wai Ha Lee
1
@WaiHaLee Sie können die Erweiterungsmethode CodeJam.EnumHelper.IsFlagSet verwenden, die mit Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_
22

@ Nidonocu

Verwenden Sie den OR-Zuweisungsoperator, um einem vorhandenen Wertesatz ein weiteres Flag hinzuzufügen.

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));
steve_c
quelle
18

Wenn ich mit Flags arbeite, deklariere ich oft zusätzliche Keine und Alle Elemente. Diese sind hilfreich, um zu überprüfen, ob alle Flags gesetzt sind oder kein Flag gesetzt ist.

[Flags] 
enum SuitsFlags { 

    None =     0,

    Spades =   1 << 0, 
    Clubs =    1 << 1, 
    Diamonds = 1 << 2, 
    Hearts =   1 << 3,

    All =      ~(~0 << 4)

}

Verwendungszweck:

Spades | Clubs | Diamonds | Hearts == All  // true
Spades & Clubs == None  // true

 
Update 2019-10:

Seit C # 7.0 können Sie binäre Literale verwenden, die wahrscheinlich intuitiver zu lesen sind:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

    Spades =   0b0001, 
    Clubs =    0b0010, 
    Diamonds = 0b0100, 
    Hearts =   0b1000,

    All =      0b1111

}
Jpsy
quelle
17

Hinzufügen Mode.Write:

Mode = Mode | Mode.Write;
David Wengier
quelle
57
oder Mode | = Mode.Write
abatishchev
14

Es gibt etwas allzu ausführliche mir über das if ((x & y) == y)...Konstrukt, vor allem , wenn xAND ysind beide Verbindung Sätze von Flaggen und Sie wollen nur wissen , ob es irgendeine Überlappung.

In diesem Fall müssen Sie nur wissen, ob nach der Bitmaske ein Wert ungleich Null [1] vorliegt .

[1] Siehe Jaimes Kommentar. Wenn wir eine authentische Bitmaske verwenden würden , müssten wir nur überprüfen, ob das Ergebnis positiv ist. Aber da enums in Kombination mit dem [Flags] Attribut sogar seltsamerweise negativ sein kann , ist es defensiv, != 0eher für als zu codieren > 0.

Aufbauend auf @ andnils Setup ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BitFlagPlay
{
    class Program
    {
        [Flags]
        public enum MyColor
        {
            Yellow = 0x01,
            Green = 0x02,
            Red = 0x04,
            Blue = 0x08
        }

        static void Main(string[] args)
        {
            var myColor = MyColor.Yellow | MyColor.Blue;
            var acceptableColors = MyColor.Yellow | MyColor.Red;

            Console.WriteLine((myColor & MyColor.Blue) != 0);     // True
            Console.WriteLine((myColor & MyColor.Red) != 0);      // False                
            Console.WriteLine((myColor & acceptableColors) != 0); // True
            // ... though only Yellow is shared.

            Console.WriteLine((myColor & MyColor.Green) != 0);    // Wait a minute... ;^D

            Console.Read();
        }
    }
}
Ruffin
quelle
Enum kann auf einem signierten Typ basieren, daher sollten Sie "! = 0" anstelle von "> 0" verwenden.
Rabe
@JaimePardos - Solange wir sie ehrlich halten, wie ich es in diesem Beispiel tue, gibt es kein Konzept von negativ. Nur 0 bis 255. Wie MSDN warnt : "Seien Sie vorsichtig, wenn Sie eine negative Zahl als Flag-Konstante definieren, da ... [das] Ihren Code verwirrend machen und zu Codierungsfehlern führen kann." Es ist seltsam, in "negativen Bitflags" zu denken! ; ^) Ich werde gleich mehr bearbeiten. Aber Sie haben Recht, wenn wir negative Werte in unseren verwenden enum, müssen wir prüfen, ob != 0.
Ruffin
10

Mit Flags können Sie Bitmasking in Ihrer Aufzählung verwenden. Auf diese Weise können Sie Aufzählungswerte kombinieren und gleichzeitig die angegebenen Werte beibehalten.

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}
Jay Mooney
quelle
0

Entschuldigung, wenn jemand dieses Szenario bereits bemerkt hat. Ein perfektes Beispiel für Flaggen, die wir in der Reflexion sehen können. Ja Bindungsflaggen ENUM. https://docs.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=netframework-4.8

[System.Flags]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public enum BindingFlags

Verwendungszweck

             // BindingFlags.InvokeMethod
            // Call a static method.
            Type t = typeof (TestClass);

            Console.WriteLine();
            Console.WriteLine("Invoking a static method.");
            Console.WriteLine("-------------------------");
            t.InvokeMember ("SayHello", BindingFlags.InvokeMethod | BindingFlags.Public | 
                BindingFlags.Static, null, null, new object [] {});
kbvishnu
quelle