2010-11-13 13 views
103

Se ho una variabile che contiene un enumerazione di flag, posso in qualche modo scorrere i valori di bit in quella specifica variabile? O devo usare Enum.GetValues ​​per iterare sull'intero enum e controllare quali sono impostati?Iterate sui valori in Flags Enum?

+0

Se si ha il controllo sulla propria API, evitare l'uso di flag di bit. Raramente sono un'ottimizzazione utile. L'utilizzo di una struct con diversi campi "bool" pubblici è semanticamente equivalente ma il tuo codice è molto più semplice. E se è necessario in seguito, è possibile modificare i campi in proprietà che manipolano internamente i campi di bit, incapsulando l'ottimizzazione. –

+0

Vedo quello che stai dicendo, e in molti casi avrebbe senso, ma in questo caso, avrei lo stesso problema del suggerimento If ...Dovrei scrivere istruzioni If per una dozzina di bool diversi invece di usare un semplice ciclo foreach su un array. (E poiché questo fa parte di una DLL pubblica, non posso fare cose arcane come avere una serie di bool o cosa hai.) – RobinHood70

+0

possibile duplicato di [Metodo di estensione generico per vedere se un enum contiene una bandiera] (http : //stackoverflow.com/questions/4108828/generic-extension-method-to-see-if-an-enum-taintain-a-flag) – nawfal

risposta

21

Tornando a questo un paio di anni più tardi, con un po 'più di esperienza, la mia risposta definitiva per solo valori singoli bit, passando dal bit più basso al più alto bit, è una leggera variante di Jeff Mercado interna di routine:

public static IEnumerable<Enum> GetUniqueFlags(this Enum flags) 
{ 
    ulong flag = 1; 
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>()) 
    { 
     ulong bits = Convert.ToUInt64(value); 
     while (flag < bits) 
     { 
      flag <<= 1; 
     } 

     if (flag == bits && flags.HasFlag(value)) 
     { 
      yield return value; 
     } 
    } 
} 

sembra funzionare, e nonostante le mie obiezioni di alcuni anni fa, io uso hasFlag qui, dal momento che è molto più leggibile rispetto all'utilizzo di confronto bit per bit e la differenza di velocità è insignificante per qualsiasi cosa farò. (È del tutto possibile che abbiano migliorato la velocità di HasFlags da allora comunque, per quel che ne so ... non ho provato.)

+0

Solo un flag di nota è inizializzato ad un int dovrebbe essere ad un ulong come bit, dovrebbe essere inizializzato come * 1ul * – forcewill

+0

Grazie, lo aggiusterò! (Sono appena andato a controllare il mio codice reale e l'ho già risolto in senso contrario dichiarando espressamente che è un ulong.) – RobinHood70

+2

Questa è l'unica soluzione che ho trovato che sembra anche non soffrire del fatto che se tu avere un flag con valore zero, che dovrebbe rappresentare "None", altre risposte I metodi GetFlag() restituiranno YourEnum.None come uno dei flag anche se non si trova effettivamente nell'enum su cui si sta eseguendo il metodo! Avevo delle strane voci di registro duplicate perché i metodi venivano eseguiti più volte di quanto mi aspettassi quando avevano un solo set di enum non zero. Grazie per aver dedicato del tempo per aggiornare e aggiungere questa grande soluzione! – BrianHall

36

Non ci sono metodi AFAIK per ottenere ogni componente. Ecco un modo si possono ottenere:

[Flags] 
enum Items 
{ 
    None = 0x0, 
    Foo = 0x1, 
    Bar = 0x2, 
    Baz = 0x4, 
    Boo = 0x6, 
} 

var value = Items.Foo | Items.Bar; 
var values = value.ToString() 
        .Split(new[] { ", " }, StringSplitOptions.None) 
        .Select(v => (Items)Enum.Parse(typeof(Items), v)); 

// This method will always end up with the most applicable values 
value = Items.Bar | Items.Baz; 
values = value.ToString() 
       .Split(new[] { ", " }, StringSplitOptions.None) 
       .Select(v => (Items)Enum.Parse(typeof(Items), v)); // Boo 

Ho adattato quello Enum fa internamente per generare la stringa di restituire invece le bandiere. Puoi guardare il codice nel riflettore e dovrebbe essere più o meno equivalente. Funziona bene per i casi di uso generale dove ci sono valori che contengono più bit.

static class EnumExtensions 
{ 
    public static IEnumerable<Enum> GetFlags(this Enum value) 
    { 
     return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray()); 
    } 

    public static IEnumerable<Enum> GetIndividualFlags(this Enum value) 
    { 
     return GetFlags(value, GetFlagValues(value.GetType()).ToArray()); 
    } 

    private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values) 
    { 
     ulong bits = Convert.ToUInt64(value); 
     List<Enum> results = new List<Enum>(); 
     for (int i = values.Length - 1; i >= 0; i--) 
     { 
      ulong mask = Convert.ToUInt64(values[i]); 
      if (i == 0 && mask == 0L) 
       break; 
      if ((bits & mask) == mask) 
      { 
       results.Add(values[i]); 
       bits -= mask; 
      } 
     } 
     if (bits != 0L) 
      return Enumerable.Empty<Enum>(); 
     if (Convert.ToUInt64(value) != 0L) 
      return results.Reverse<Enum>(); 
     if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L) 
      return values.Take(1); 
     return Enumerable.Empty<Enum>(); 
    } 

    private static IEnumerable<Enum> GetFlagValues(Type enumType) 
    { 
     ulong flag = 0x1; 
     foreach (var value in Enum.GetValues(enumType).Cast<Enum>()) 
     { 
      ulong bits = Convert.ToUInt64(value); 
      if (bits == 0L) 
       //yield return value; 
       continue; // skip the zero value 
      while (flag < bits) flag <<= 1; 
      if (flag == bits) 
       yield return value; 
     } 
    } 
} 

Il metodo di estensione GetIndividualFlags() ottiene tutti i singoli bandiere per un tipo. Quindi i valori contenenti più bit sono esclusi.

var value = Items.Bar | Items.Baz; 
value.GetFlags();   // Boo 
value.GetIndividualFlags(); // Bar, Baz 
+0

Avevo preso in considerazione l'idea di eseguire uno split split, ma questo probabilmente è un overhead molto più che semplicemente l'iterazione dei valori bit dell'intero enum. – RobinHood70

+0

Sfortunatamente così facendo, dovresti testare i valori ridondanti (se non li volessi). Vedi il mio secondo esempio, produrrebbe 'Bar',' Baz' e 'Boo' invece di solo' Boo'. –

+0

Interessante che tu riesca a far uscire Boo da quello, anche se quella parte non è necessaria (e in effetti, una pessima idea :)) per quello che sto facendo. – RobinHood70

2

Non è necessario ripetere tutti i valori. basta controllare i flag specifici in questo modo:

if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1) 
{ 
    //do something... 
} 

o (come detto pstrjds nei commenti), è possibile verificare la presenza di utilizzarlo come:

if(myVar.HasFlag(FlagsEnum.Flag1)) 
{ 
    //do something... 
} 
+5

Se stai usando .Net 4.0 c'è un metodo di estensione HasFlag che puoi usare per fare la stessa cosa: myVar.HasFlag (FlagsEnum.Flag1) – pstrjds

+0

grazie ... non ho mai visto quello che dici, ma dovrebbe essere così bravo ... :) –

+1

Se un programmatore non riesce a capire un'operazione AND bit a bit, dovrebbe comprimerlo e trovare una nuova carriera. –

1

È possibile utilizzare un Iterator dal Enum. A partire dal codice MSDN:

public class DaysOfTheWeek : System.Collections.IEnumerable 
{ 
    int[] dayflag = { 1, 2, 4, 8, 16, 32, 64 }; 
    string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; 
    public string value { get; set; } 

    public System.Collections.IEnumerator GetEnumerator() 
    { 
     for (int i = 0; i < days.Length; i++) 
     { 
      if value >> i & 1 == dayflag[i] { 
       yield return days[i]; 
      } 
     } 
    } 
} 

Non è testato, quindi se ho fatto un errore non esitate a chiamarmi. (ovviamente non è ri-entrante.) Dovresti assegnare un valore in anticipo, o scomporlo in un'altra funzione che usa enum.dayflag ed enum.days. Potresti essere in grado di andare da qualche parte con il contorno.

153
static IEnumerable<Enum> GetFlags(Enum input) 
{ 
    foreach (Enum value in Enum.GetValues(input.GetType())) 
     if (input.HasFlag(value)) 
      yield return value; 
} 
+0

Questo è vicino a quello che sto facendo, anche se non avevo pensato di costruire il mio enumeratore (o noto che potevo). Sono ancora abbastanza nuovo per C#, quindi questa è stata sicuramente una risposta educativa per me. Grazie! – RobinHood70

+5

Si noti che 'HasFlag' è disponibile da .NET 4 in poi. –

+3

Questo è fantastico! Ma puoi renderlo ancora più semplice e facile da usare. Solo bastone questo come un metodo di estensione: 'Enum.GetValues ​​(input.GetType()) Cast () .Dove (input.HasFlag);' Poi basta:. 'MyEnum.GetFLags()' :) – joshcomley

3

Non soddisfatto delle risposte di cui sopra, sebbene fossero l'inizio.

Dopo mettendo insieme alcune fonti differenti qui:
Previous poster in this thread's SO QnA
Code Project Enum Flags Check Post
Great Enum<T> Utility

Ho creato questo modo fatemi sapere cosa ne pensate.
Parametri:
bool checkZero: indica di consentire 0 come valore di contrassegno. Per impostazione predefinita, input = 0 restituisce vuoto.
bool checkFlags: indica per verificare se lo Enum è decorato con l'attributo [Flags].
PS. Non ho tempo ora per capire l'alg checkCombinators = false che lo costringerà a ignorare tutti i valori enum che sono combinazioni di bit.

public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum input, bool checkZero = false, bool checkFlags = true, bool checkCombinators = true) 
    { 
     Type enumType = typeof(TEnum); 
     if (!enumType.IsEnum) 
      yield break; 

     ulong setBits = Convert.ToUInt64(input); 
     // if no flags are set, return empty 
     if (!checkZero && (0 == setBits)) 
      yield break; 

     // if it's not a flag enum, return empty 
     if (checkFlags && !input.GetType().IsDefined(typeof(FlagsAttribute), false)) 
      yield break; 

     if (checkCombinators) 
     { 
      // check each enum value mask if it is in input bits 
      foreach (TEnum value in Enum<TEnum>.GetValues()) 
      { 
       ulong valMask = Convert.ToUInt64(value); 

       if ((setBits & valMask) == valMask) 
        yield return value; 
      } 
     } 
     else 
     { 
      // check each enum value mask if it is in input bits 
      foreach (TEnum value in Enum <TEnum>.GetValues()) 
      { 
       ulong valMask = Convert.ToUInt64(value); 

       if ((setBits & valMask) == valMask) 
        yield return value; 
      } 
     } 

    } 

Questo fa uso della classe di supporto Enum<T> found here che ho aggiornato da usare yield return per GetValues:

public static class Enum<TEnum> 
{ 
    public static TEnum Parse(string value) 
    { 
     return (TEnum)Enum.Parse(typeof(TEnum), value); 
    } 

    public static IEnumerable<TEnum> GetValues() 
    { 
     foreach (object value in Enum.GetValues(typeof(TEnum))) 
      yield return ((TEnum)value); 
    } 
} 

Infine, ecco un esempio di utilizzo di esso:

private List<CountType> GetCountTypes(CountType countTypes) 
    { 
     List<CountType> cts = new List<CountType>(); 

     foreach (var ct in countTypes.GetFlags()) 
      cts.Add(ct); 

     return cts; 
    } 
+0

Mi spiace, non ho avuto il tempo di guardare questo progetto tra diversi giorni. Ti risponderò una volta che avrò dato un'occhiata al tuo codice. – RobinHood70

+3

Solo un avviso che c'è un bug in quel codice. Il codice in entrambi i rami dell'istruzione if (checkCombinators) è identico. Inoltre, forse non un bug, ma inaspettato, è che se si ha un valore enum dichiarato per 0, verrà sempre restituito nella raccolta. Sembra che dovrebbe essere restituito solo se checkZero è true e non ci sono altri flag impostati. – dhochee

+0

@dhochee. Sono d'accordo. O il codice è piuttosto carino, ma gli argomenti sono confusi. – AFract

2

Quello che ho fatto è stato cambiare il mio approccio, invece di digitare il parametro di input del metodo come il tipo enum, l'ho digitato come un array del enum tipo (MyEnum[] myEnums), in questo modo ho solo scorrere l'array con un'istruzione switch all'interno del ciclo.

2

Basandosi sulla risposta di Greg sopra, questo si occupa anche del caso in cui si ha un valore 0 nel proprio enum, come None = 0. In tal caso, non dovrebbe iterare su quel valore.

public static IEnumerable<Enum> ToEnumerable(this Enum input) 
{ 
    foreach (Enum value in Enum.GetValues(input.GetType())) 
     if (input.HasFlag(value) && Convert.ToInt64(value) != 0) 
      yield return value; 
} 

Qualcuno sapere come migliorare su questo ancora di più in modo che possa gestire il caso in cui sono fissati in un modo super intelligente in grado di gestire tutti i sottostanti tipo enum e il caso di tutti i = tutte le bandiere del enum ~ 0 e Tutti = EnumValue1 | EnumValue2 | EnumValue3 | ...

22

Ecco una soluzione Linq al problema.

public static IEnumerable<Enum> GetFlags(this Enum e) 
{ 
     return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag); 
} 
+0

Perché non è questo in cima ?? :) Usa '.Where (v =>! Equals ((int) (oggetto) v, 0) && e.HasFlag (v));' se hai un valore zero per rappresentare 'None' – georgiosd

-2

Puoi farlo direttamente convertendo in int ma perderai il controllo del tipo. Penso che il modo migliore sia usare qualcosa di simile alla mia proposta. Mantiene il tipo corretto fino in fondo. Nessuna conversione richiesta. Non è perfetto a causa del pugilato che aggiungerà un piccolo successo in termini di prestazioni.

Non perfetta (pugilato), ma fa il lavoro senza preavviso ...

/// <summary> 
/// Return an enumerators of input flag(s) 
/// </summary> 
/// <param name="input"></param> 
/// <returns></returns> 
public static IEnumerable<T> GetFlags<T>(this T input) 
{ 
    foreach (Enum value in Enum.GetValues(input.GetType())) 
    { 
     if ((int) (object) value != 0) // Just in case somebody has defined an enum with 0. 
     { 
      if (((Enum) (object) input).HasFlag(value)) 
       yield return (T) (object) value; 
     } 
    } 
} 

Usage:

FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed; 
    foreach (FileAttributes fa in att.GetFlags()) 
    { 
     ... 
    } 
6

+1 per la risposta fornita da @ RobinHood70. Ho trovato che una versione generica del metodo era conveniente per me.

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags) 
{ 
    if (!typeof(T).IsEnum) 
     throw new ArgumentException("The generic type parameter must be an Enum."); 

    if (flags.GetType() != typeof(T)) 
     throw new ArgumentException("The generic type parameter does not match the target type."); 

    ulong flag = 1; 
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>()) 
    { 
     ulong bits = Convert.ToUInt64(value); 
     while (flag < bits) 
     { 
      flag <<= 1; 
     } 

     if (flag == bits && flags.HasFlag(value as Enum)) 
     { 
      yield return value; 
     } 
    } 
}