Die häufigsten bitweisen C # -Operationen für Aufzählungen

201

Für mein Leben kann ich mich nicht erinnern, wie ich ein bisschen in einem Bitfeld gesetzt, gelöscht, umgeschaltet oder getestet habe. Entweder bin ich mir nicht sicher oder ich vermische sie, weil ich sie selten brauche. Ein "Bit-Spickzettel" wäre also schön zu haben.

Beispielsweise:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

oder

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

Können Sie Beispiele für alle anderen gängigen Operationen geben, vorzugsweise in C # -Syntax unter Verwendung einer [Flags] -Aufzählung?

steffenj
quelle
5
Dies wurde vor beantwortet hier
Greg Rogers
7
Schade, dass dieser Link nicht in den Fragenhinweisen zu diesem Thema enthalten ist.
Cori
10
Diese Frage ist jedoch für c / c ++ markiert, sodass jemand, der nach Informationen über C # sucht, dort wahrscheinlich nicht nachsehen würde, obwohl die Syntax identisch zu sein scheint.
Adam Lassek
Mir ist kein weniger ausführlicher Weg bekannt, um den Bittest durchzuführen
Andy Johnson
2
@Andy, es gibt jetzt eine API für den Bittest in .NET 4.
Drew Noakes

Antworten:

288

Ich habe noch etwas an diesen Erweiterungen gearbeitet - den Code finden Sie hier

Ich habe einige Erweiterungsmethoden geschrieben, die System.Enum erweitern, die ich häufig verwende ... Ich behaupte nicht, dass sie kugelsicher sind, aber sie haben geholfen ... Kommentare entfernt ...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

Dann werden sie wie folgt verwendet

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false
Hugoware
quelle
1
Ich fand das auch nützlich - Irgendwelche Ideen, wie ich es so ändern kann, dass es auf jedem zugrunde liegenden Typ funktioniert?
Charlie Salts
7
Diese Erweiterungen haben gerade meinen Tag, meine Woche, meinen Monat und möglicherweise mein Jahr gemacht.
thaBadDawg
Danke dir! Alle: Schauen Sie sich unbedingt das Update an, mit dem Hugoware verlinkt hat.
Helge Klein
Eine sehr schöne Reihe von Erweiterungen. Es ist eine Schande, dass sie Boxen benötigen, obwohl ich mir keine Alternative vorstellen kann, die kein Boxen verwendet und so prägnant ist. Auch die neue HasFlagMethode Enumerfordert Boxen.
Drew Noakes
4
@Drew: Siehe code.google.com/p/unconstrained-melody für eine Möglichkeit, Boxen zu vermeiden :)
Jon Skeet
109

In .NET 4 können Sie jetzt schreiben:

flags.HasFlag(FlagsEnum.Bit4)
Drew Noakes
quelle
4
+1 für den Hinweis, obwohl FlagsEnumes ein hässlicher Name ist. :)
Jim Schubert
2
@ Jim vielleicht. Es ist nur ein Beispielname, wie er in der ursprünglichen Frage verwendet wurde. Sie können ihn also in Ihrem Code ändern.
Drew Noakes
14
Ich weiß! Aber hässliche Namen sind wie IE6 und werden wahrscheinlich nie verschwinden :(
Jim Schubert
5
@ JimSchubert, wieder habe ich gerade den Typnamen aus der ursprünglichen Frage reproduziert, um das Problem nicht zu verwirren. Die Richtlinien für die Benennung von .NET-Aufzählungstypen geben an, dass alle [Flags]Aufzählungen pluralisierte Namen haben sollten, sodass der Name FlagsEnumnoch schwerwiegendere Probleme als Hässlichkeit aufweist.
Drew Noakes
1
Ich empfehle auch Richtlinien für das Framework-Design: Konventionen, Redewendungen und Muster für wiederverwendbare .NET-Bibliotheken . Der Kauf ist etwas teuer, aber ich glaube, Safari Online und Books24x7 bieten ihn beide für Abonnenten an.
Jim Schubert
89

Die Redewendung besteht darin, den bitweisen oder gleichen Operator zum Setzen von Bits zu verwenden:

flags |= 0x04;

Um ein bisschen zu löschen, lautet die Redewendung bitweise und mit Negation:

flags &= ~0x04;

Manchmal haben Sie einen Versatz, der Ihr Bit identifiziert, und dann besteht die Redewendung darin, diese in Kombination mit der Linksverschiebung zu verwenden:

flags |= 1 << offset;
flags &= ~(1 << offset);
Stephen Deken
quelle
22

@ Zeichnete

Beachten Sie, dass Enum.HasFlag, außer in den einfachsten Fällen, im Vergleich zum manuellen Schreiben des Codes eine erhebliche Leistungseinbuße mit sich bringt. Betrachten Sie den folgenden Code:

[Flags]
public enum TestFlags
{
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256,
    Ten = 512
}


class Program
{
    static void Main(string[] args)
    {
        TestFlags f = TestFlags.Five; /* or any other enum */
        bool result = false;

        Stopwatch s = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; i++)
        {
            result |= f.HasFlag(TestFlags.Three);
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*

        s.Restart();
        for (int i = 0; i < 10000000; i++)
        {
            result |= (f & TestFlags.Three) != 0;
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*        

        Console.ReadLine();
    }
}

Über 10 Millionen Iterationen benötigt die HasFlags-Erweiterungsmethode satte 4793 ms, verglichen mit 27 ms für die standardmäßige bitweise Implementierung.

Chuck Dee
quelle
10
Während sicherlich interessant und gut darauf hinzuweisen. Sie müssen die Verwendung berücksichtigen. Wenn Sie also nicht ein paar hunderttausend oder mehr Operationen durchführen, werden Sie dies wahrscheinlich nicht einmal bemerken.
Joshua Hayes
7
Die HasFlagMethode beinhaltet Boxen / Unboxen, was diesen Unterschied erklärt. Aber die Kosten sind so gering (0,4 µs), dass ich jeden Tag den besser lesbaren (und weniger wahrscheinlich fehlerhaften) deklarativen API-Aufruf annehmen würde, wenn Sie sich nicht in einer engen Schleife befinden.
Drew Noakes
8
Je nach Verwendung kann dies ein Problem sein. Und da ich ziemlich viel mit Ladern arbeite, fand ich es gut, darauf hinzuweisen.
Chuck Dee
11

Die in .NET integrierten Flag-Enum-Operationen sind leider recht begrenzt. Meistens müssen Benutzer die bitweise Operationslogik herausfinden.

In .NET 4 wurde die Methode HasFlaghinzugefügt, Enumdie den Benutzercode vereinfacht, aber leider gibt es viele Probleme damit.

  1. HasFlag ist nicht typsicher, da es jede Art von Aufzählungswertargument akzeptiert, nicht nur den angegebenen Aufzählungstyp.
  2. HasFlagist nicht eindeutig, ob geprüft wird, ob der Wert alle oder einige der vom Argument enum value bereitgestellten Flags enthält. Es ist übrigens alles.
  3. HasFlag ist ziemlich langsam, da es Boxen erfordert, was Zuordnungen und damit mehr Speicherbereinigungen verursacht.

Zum Teil aufgrund der eingeschränkten Unterstützung von .NET für Flag-Enums habe ich die OSS-Bibliothek Enums.NET geschrieben die jedes dieser Probleme behebt und den Umgang mit Flaggenaufzählungen erheblich erleichtert.

Im Folgenden sind einige der Vorgänge aufgeführt, die zusammen mit den entsprechenden Implementierungen nur mit dem .NET-Framework bereitgestellt werden.

Flaggen kombinieren

.NETZ             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Fahnen entfernen

.NETZ             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Gemeinsame Flaggen

.NETZ             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Flaggen umschalten

.NETZ             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Hat alle Flaggen

.NET             (flags & otherFlags) == otherFlags oderflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


Hat irgendwelche Flaggen

.NETZ             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Fahnen holen

.NETZ

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


Ich versuche, diese Verbesserungen in .NET Core und möglicherweise in das vollständige .NET Framework zu integrieren. Sie können meinen Vorschlag hier überprüfen .

TylerBrinkley
quelle
7

C ++ - Syntax unter der Annahme, dass Bit 0 LSB ist, unter der Annahme, dass Flags lange ohne Vorzeichen sind:

Überprüfen Sie, ob Set:

flags & (1UL << (bit to test# - 1))

Überprüfen Sie, ob nicht festgelegt:

invert test !(flag & (...))

Einstellen:

flag |= (1UL << (bit to set# - 1))

Klar:

flag &= ~(1UL << (bit to clear# - 1))

Umschalten:

flag ^= (1UL << (bit to set# - 1))
Petesh
quelle
3

Verwenden Sie Folgendes, um die beste Leistung und keinen Müll zu erzielen:

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}
Mark Bamford
quelle
2

Um ein Bit zu testen, gehen Sie wie folgt vor: (Angenommen, Flags sind eine 32-Bit-Zahl)

Testbit:

if((flags & 0x08) == 0x08)
(Wenn Bit 4 gesetzt ist, ist es wahr.) Zurückschalten (1 - 0 oder 0 - 1):
flags = flags ^ 0x08;
Setzen Sie Bit 4 auf Null zurück:
flags = flags & 0xFFFFFF7F;

Nashirak
quelle
2
-1 da stört das nicht mal mit enums? Außerdem ist die Handcodierung der Werte fragil ... Ich würde zumindest schreiben ~0x08anstatt 0xFFFFFFF7... (die eigentliche Maske für 0x8)
Ben Mosher
1
Zuerst dachte ich, dass Bens -1 hart war, aber die Verwendung von "0xFFFFFF7F" macht dies zu einem besonders schlechten Beispiel.
ToolmakerSteve
2

Dies wurde durch die Verwendung von Sets als Indexer in Delphi inspiriert, als:

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}
Tony Tanzillo
quelle
2
Dies wird auch dann nicht kompiliert, wenn BindingFlags eine Byte-Aufzählung ist: this.flags & = ~ index;
eigenartig
0

C ++ - Operationen sind: & | ^ ~ (für und oder oder xoder und nicht bitweise Operationen). Interessant sind auch >> und <<, bei denen es sich um Bitshift-Operationen handelt.

Um zu testen, ob ein Bit in einem Flag gesetzt ist, verwenden Sie: if (flags & 8) // testet Bit 4 wurde gesetzt

workmad3
quelle
8
Frage bezieht sich auf c #, nicht c ++
Andy Johnson
3
Auf der anderen Seite, verwendet C # die gleichen Operatoren: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve
3
Zur Verteidigung von @ workmad3 hatten die ursprünglichen Tags C und C ++
pqsk