Operazioni bit per bit C # più comuni su enum


201

Per la mia vita, non riesco a ricordare come impostare, eliminare, attivare o disattivare un po 'in un campo bit. O non sono sicuro o li confondo perché raramente ne ho bisogno. Quindi un "bit-cheat-sheet" sarebbe bello avere.

Per esempio:

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

o

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

Puoi fornire esempi di tutte le altre operazioni comuni, preferibilmente nella sintassi C # usando un enum [Flags]?


5
Questa è stata risposta prima qui
Greg Rogers,

7
peccato che il link non compaia nei suggerimenti delle domande per questo argomento.
cori,

10
Quella domanda è taggata per c / c ++, tuttavia, quindi qualcuno che cerca informazioni su C # probabilmente non guarderebbe lì anche se la sintassi sembra essere la stessa.
Adam Lassek,

Non sono a conoscenza di un modo meno dettagliato per eseguire il bit test
Andy Johnson,

2
@Andy, ora c'è un'API per il bit test in .NET 4.
Drew Noakes,

Risposte:


288

Ho lavorato ancora su queste estensioni - Puoi trovare il codice qui

Ho scritto alcuni metodi di estensione che estendono System.Enum che uso spesso ... Non sto affermando che sono a prova di proiettile, ma hanno aiutato ... Commenti rimossi ...

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

    }
}

Quindi vengono utilizzati come segue

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
Ho anche trovato questo utile - Qualche idea su come posso modificarlo in modo che funzioni su qualsiasi tipo sottostante?
Charlie Salts,

7
Queste estensioni hanno appena reso il mio giorno, la mia settimana, il mio mese e molto probabilmente il mio anno.
thaBadDawg

Grazie! Tutti: assicuratevi di controllare l'aggiornamento a cui Hugoware è collegato.
Helge Klein,

Un bel set di estensioni. È un peccato che richiedano la boxe, anche se non riesco a pensare a un'alternativa che non usi la boxe ed è così concisa. Anche il nuovo HasFlagmetodo Enumrichiede boxe.
Drew Noakes,

4
@Drew: vedi code.google.com/p/unconstrained-melody per un modo di evitare la boxe :)
Jon Skeet,

109

In .NET 4 ora puoi scrivere:

flags.HasFlag(FlagsEnum.Bit4)

4
+1 per dirlo, anche se FlagsEnumè un brutto nome. :)
Jim Schubert,

2
@Jim, forse. È solo un nome di esempio, come usato nella domanda originale, quindi sei libero di cambiarlo nel tuo codice.
Drew Noakes,

14
Lo so! Ma i nomi brutti sono come IE6 e probabilmente non spariranno mai :(
Jim Schubert,

5
@JimSchubert, ancora una volta, ho appena riprodotto il nome del tipo dalla domanda originale per non confondere il problema. Le Linee guida per la denominazione dei tipi di enumerazione .NET indicano che tutti gli [Flags]enum dovrebbero avere nomi pluralizzati, quindi il nome FlagsEnumha problemi ancora più gravi della bruttezza.
Ha disegnato Noakes il

1
Consiglio anche le linee guida per la progettazione di framework: convenzioni, modi di dire e schemi per librerie .NET riutilizzabili . È un po 'costoso da acquistare, ma credo che Safari Online e Books24x7 lo offrano entrambi per gli abbonati.
Jim Schubert,

89

Il linguaggio è usare l'operatore bit per bit o uguale per impostare i bit:

flags |= 0x04;

Per chiarire un po ', il linguaggio è usare bit a bit e con negazione:

flags &= ~0x04;

A volte hai un offset che identifica il tuo bit, e quindi il linguaggio è quello di usarli in combinazione con lo spostamento a sinistra:

flags |= 1 << offset;
flags &= ~(1 << offset);

22

@Ha disegnato

Si noti che, tranne nel caso più semplice, Enum.HasFlag comporta una forte penalizzazione delle prestazioni rispetto alla scrittura manuale del codice. Considera il seguente codice:

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

Oltre 10 milioni di iterazioni, il metodo di estensione HasFlags richiede ben 4793 ms, rispetto ai 27 ms per l'implementazione bit a bit standard.


10
Sebbene certamente interessante e bello da sottolineare. È necessario considerare l'utilizzo. In base a questo, se non esegui un paio di centinaia di migliaia o più di operazioni, probabilmente non lo noterai nemmeno.
Joshua Hayes,

7
Il HasFlagmetodo prevede il boxing / unboxing, che rappresenta questa differenza. Ma il costo è così banale (0,4 µs) che, a meno che tu non sia in un circuito ristretto, prenderei la chiamata API dichiarativa più leggibile (e meno probabile) ogni giorno.
Drew Noakes,

8
A seconda dell'utilizzo, potrebbe essere un problema. E dal momento che lavoro un po 'con i caricatori, ho pensato che fosse bello sottolineare.
Chuck Dee,

11

Le operazioni di enum di flag integrate di .NET sono purtroppo piuttosto limitate. La maggior parte delle volte gli utenti non riescono a capire la logica dell'operazione bit a bit.

In .NET 4 è HasFlagstato aggiunto il metodo Enumche aiuta a semplificare il codice dell'utente, ma sfortunatamente ci sono molti problemi con esso.

  1. HasFlag non è sicuro per i tipi in quanto accetta qualsiasi tipo di argomento del valore enum, non solo il tipo enum specificato.
  2. HasFlagè ambiguo se verifica se il valore ha tutti o alcuni dei flag forniti dall'argomento valore enum. A proposito.
  3. HasFlag è piuttosto lento in quanto richiede il pugilato che causa allocazioni e quindi più raccolte di rifiuti.

A causa in parte del limitato supporto di .NET per gli enumeratori di flag, ho scritto la libreria OSS Enums.NET che affronta ciascuno di questi problemi e semplifica la gestione degli enumer di flag.

Di seguito sono riportate alcune delle operazioni fornite insieme alle loro implementazioni equivalenti utilizzando solo il framework .NET.

Combina bandiere

.NETTO             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Rimuovi bandiere

.NETTO             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Bandiere comuni

.NETTO             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Attiva / disattiva bandiere

.NETTO             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Ha tutte le bandiere

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

Enums.NET flags.HasAllFlags(otherFlags)


Ha qualche bandiera

.NETTO             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Ottieni bandiere

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


Sto cercando di ottenere questi miglioramenti incorporati in .NET Core e forse alla fine il .NET Framework completo. Puoi dare un'occhiata alla mia proposta qui .


7

Sintassi C ++, supponendo che il bit 0 sia LSB, supponendo che i flag siano senza segno lunghi:

Controlla se impostato:

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

Verifica se non impostato:

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

Impostato:

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

Chiaro:

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

Toggle:

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

3

Per prestazioni ottimali e immondizia zero, utilizzare questo:

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

Per testare un po 'dovresti fare quanto segue: (supponendo che i flag siano un numero a 32 bit)

Bit di prova:

if((flags & 0x08) == 0x08)
(Se il bit 4 è impostato, allora è vero) Toggle Back (1 - 0 o 0 - 1):
flags = flags ^ 0x08;
Ripristina Bit 4 su Zero:
flags = flags & 0xFFFFFF7F;


2
-1 dal momento che questo non si preoccupa nemmeno di enumerazioni? Inoltre, scrivere a mano i valori è fragile ... Scriverei almeno ~0x08invece di 0xFFFFFFF7... (la maschera effettiva per 0x8)
Ben Mosher,

1
All'inizio pensavo che il -1 di Ben fosse duro, ma l'uso di "0xFFFFFF7F" lo rende un esempio particolarmente scarso.
ToolmakerSteve

2

Questo è stato ispirato dall'uso dei set come indicizzatori in Delphi, molto tempo fa:

/// 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
Questo non viene nemmeno compilato se BindingFlags è byte enum: this.flags & = ~ index;
amuliare il

0

Le operazioni C ++ sono: & | ^ ~ (per e, o, xor e non operazioni bit a bit). Anche di interesse sono >> e <<, che sono operazioni di bitshift.

Quindi, per verificare se un bit è impostato in un flag, dovresti usare: if (flags & 8) // testing bit 4 è stato impostato


8
La domanda riguarda c #, non c ++
Andy Johnson,

3
D'altra parte, C # utilizza gli stessi operatori: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
A difesa di @ workmad3, i tag originali avevano C e C ++
pqsk,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.