Najczęstsze operacje bitowe C # na wyliczeniach

201

Przez całe życie nie pamiętam, jak ustawić, usunąć, przełączyć lub przetestować trochę na polu bitowym. Albo nie jestem pewien, albo je mieszam, ponieważ rzadko ich potrzebuję. Tak więc byłoby miło mieć „ściągawkę”.

Na przykład:

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

lub

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

Czy możesz podać przykłady wszystkich innych typowych operacji, najlepiej w składni C # przy użyciu wyliczenia [Flagi]?

steffenj
źródło
5
Na to pytanie udzielono już odpowiedzi tutaj
Greg Rogers
7
szkoda, że ​​ten link nie pojawia się w podpowiedziach na ten temat.
cori
10
To pytanie jest oznaczone jako c / c ++, więc ktoś szukający informacji o C # prawdopodobnie nie szukałby tam, nawet jeśli składnia wydaje się być taka sama.
Adam Lassek,
Nie znam mniej szczegółowego sposobu przeprowadzenia testu bitów
Andy Johnson
2
@Andy, teraz jest interfejs API dla testu bitów w .NET 4.
Drew Noakes

Odpowiedzi:

288

Popracowałem trochę nad tymi rozszerzeniami - kod można znaleźć tutaj

Napisałem kilka metod rozszerzenia, które rozszerzają System.Enum, których często używam ... Nie twierdzę, że są kuloodporne, ale pomogły ... Komentarze zostały usunięte ...

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);
            }  
        }

    }
}

Następnie są używane w następujący sposób

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
źródło
1
Uznałem to również za przydatne - Jakieś pomysły, jak mogę je zmodyfikować, aby działały na dowolnym typie bazowym?
Charlie Salts
7
Te rozszerzenia sprawiły, że mój dzień, tydzień, miesiąc, a być może mój rok.
thaBadDawg
Dziękuję Ci! Wszyscy: koniecznie sprawdź aktualizację, z którą powiązał Hugoware.
Helge Klein
Bardzo ładny zestaw rozszerzeń. Szkoda, że ​​wymagają boksu, choć nie mogę wymyślić alternatywy, która nie używa boksu i jest taka zwięzła. Nawet nowa HasFlagmetoda Enumwymaga boksu.
Drew Noakes
4
@Drew: Zobacz code.google.com/p/unconstrained-melody, aby dowiedzieć się, jak uniknąć boksu :)
Jon Skeet
109

W .NET 4 możesz teraz pisać:

flags.HasFlag(FlagsEnum.Bit4)
Drew Noakes
źródło
4
+1 za wskazanie tego, chociaż FlagsEnumto brzydkie imię. :)
Jim Schubert,
2
@ Jim, być może. To tylko przykładowa nazwa użyta w pierwotnym pytaniu, więc możesz ją zmienić w kodzie.
Drew Noakes,
14
Wiem! Ale brzydkie imiona są jak IE6 i prawdopodobnie nigdy nie znikną :(
Jim Schubert
5
@JimSchubert, ponownie, właśnie odtworzyłem nazwę typu z pierwotnego pytania, aby nie pomylić problemu. W nazewnictwa Wytyczne .NET Wyliczanie Rodzaj wskazują, że wszystkie [Flags]teksty stałe powinny mieć nazwy w liczbie mnogiej, więc nazwa FlagsEnumjest jeszcze bardziej poważne problemy niż brzydotę.
Drew Noakes,
1
Polecam także Wytyczne projektowania ram: konwencje, idiomy i wzorce dla bibliotek .NET wielokrotnego użytku . Jest trochę drogi, ale uważam, że Safari Online i Books24x7 oferują ją subskrybentom.
Jim Schubert
89

Idiom polega na użyciu operatora bitowego lub równego do ustawienia bitów:

flags |= 0x04;

Aby trochę wyczyścić, idiomem jest użycie bitowe i z negacją:

flags &= ~0x04;

Czasami masz przesunięcie, które identyfikuje twój bit, a następnie idiomem jest użycie ich w połączeniu z przesunięciem w lewo:

flags |= 1 << offset;
flags &= ~(1 << offset);
Stephen Deken
źródło
22

@Rysował

Zauważ, że z wyjątkiem najprostszych przypadków Enum.HasFlag niesie ze sobą wysoką wydajność w porównaniu do ręcznego pisania kodu. Rozważ następujący kod:

[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();
    }
}

Ponad 10 milionów iteracji metoda rozszerzenia HasFlags zajmuje aż 4793 ms, w porównaniu do 27 ms dla standardowej implementacji bitowej.

Chuck Dee
źródło
10
Choć z pewnością interesujące i warte podkreślenia. Musisz wziąć pod uwagę użycie. Zgodnie z tym, jeśli nie wykonujesz kilkuset tysięcy operacji więcej, prawdopodobnie nawet tego nie zauważysz.
Joshua Hayes
7
HasFlagMetoda polega boks / unboxing, co stanowi dla tej różnicy. Ale koszt jest tak trywialny (0,4 µs), że jeśli nie będziesz w ścisłej pętli, skorzystam z bardziej czytelnego (i mniej prawdopodobnego błędnego) deklaratywnego wywołania API każdego dnia.
Drew Noakes,
8
W zależności od użytkowania może to stanowić problem. A ponieważ dość często pracuję z ładowarkami, pomyślałem, że warto to podkreślić.
Chuck Dee,
11

Wbudowane operacje wyliczania flag .NET są niestety dość ograniczone. Przez większość czasu użytkownicy nie są w stanie zrozumieć logiki operacji bitowej.

W .NET 4 HasFlagdodano metodę, Enumktóra pomaga uprościć kod użytkownika, ale niestety jest z nim wiele problemów.

  1. HasFlag nie jest bezpieczny dla typu, ponieważ przyjmuje dowolny typ argumentu wartości wyliczeniowej, nie tylko dany typ wyliczenia.
  2. HasFlagjest niejednoznaczny, czy sprawdza, czy wartość ma wszystkie czy jakiekolwiek flagi dostarczone przez argument wartości wyliczeniowej. Nawiasem mówiąc, to wszystko.
  3. HasFlag jest raczej powolny, ponieważ wymaga boksu, który powoduje przydziały, a tym samym więcej śmieci.

Po części ze względu na ograniczone wsparcie .NET dla wyliczeń flag, napisałem bibliotekę OSS Enums.NET, która rozwiązuje każdy z tych problemów i znacznie ułatwia radzenie sobie z wyliczeniami flag.

Poniżej znajdują się niektóre operacje, które udostępnia wraz z ich równoważnymi implementacjami przy użyciu samego środowiska .NET.

Połącz flagi

.NETTO             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Usuń flagi

.NETTO             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Wspólne flagi

.NETTO             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Przełącz flagi

.NETTO             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Ma wszystkie flagi

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

Enums.NET flags.HasAllFlags(otherFlags)


Ma dowolne flagi

.NETTO             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Zdobądź flagi

.NETTO

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()


Usiłuję włączyć te ulepszenia do .NET Core i być może pełnej wersji .NET Framework. Możesz sprawdzić moją propozycję tutaj .

TylerBrinkley
źródło
7

Składnia C ++, przy założeniu, że bit 0 to LSB, przy założeniu, że flagi są bez znaku długie:

Sprawdź, czy zestaw:

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

Sprawdź, jeśli nie ustawiono:

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

Zestaw:

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

Jasny:

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

Przełącznik:

flag ^= (1UL << (bit to set# - 1))
Petesh
źródło
3

Aby uzyskać najlepszą wydajność i zero śmieci, użyj tego:

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
źródło
2

Aby przetestować bit, wykonaj następujące czynności: (zakładając, że flagi są liczbą 32-bitową)

Bit testowy:

if((flags & 0x08) == 0x08)
(Jeśli bit 4 jest ustawiony, to jego prawda) Przełącz wstecz (1 - 0 lub 0 - 1):
flags = flags ^ 0x08;
Zresetuj Bit 4 do Zera:
flags = flags & 0xFFFFFF7F;

Nashirak
źródło
2
-1, skoro to nawet nie przeszkadza w wyliczeniach? Dodatkowo ręczne kodowanie wartości jest kruche ... Przynajmniej napisałbym ~0x08zamiast 0xFFFFFFF7... (rzeczywista maska ​​dla 0x8)
Ben Mosher
1
Na początku myślałem, że Ben -1 był trudny, ale użycie „0xFFFFFF7F” sprawia, że ​​jest to szczególnie słaby przykład.
ToolmakerSteve
2

Zostało to zainspirowane użyciem Setów jako indeksatorów w Delphi, gdy:

/// 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
źródło
2
To nawet się nie kompiluje, jeśli BindingFlags jest bajtem enum: this.flags & = ~ index;
amuliar
0

Operacje w C ++ to: & | ^ ~ (dla i, lub, xor i nie bitowych). Interesujące są również >> i <<, które są operacjami przesunięcia bitów.

Tak więc, aby sprawdzić, czy bit jest ustawiony w fladze, użyłbyś: if (flagi i 8) // testy bit 4 został ustawiony

workmad3
źródło
8
Pytanie dotyczy c #, a nie c ++
Andy Johnson
3
Z drugiej strony, C # używa tych samych operatorów: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve
3
W obronie @ workmad3 oryginalne tagi miały C i C ++
pqsk