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]?


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

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)

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

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.


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 .


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

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

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;


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];

  }
}

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.