Iterare sui valori in Flags Enum?

Se ho una variabile contenente un enumerazione di flags, 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?

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

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

 [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 ciò che Enum fa internamente per generare la stringa per restituire invece i flag. 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 GetFlags(this Enum value) { return GetFlags(value, Enum.GetValues(value.GetType()).Cast().ToArray()); } public static IEnumerable GetIndividualFlags(this Enum value) { return GetFlags(value, GetFlagValues(value.GetType()).ToArray()); } private static IEnumerable GetFlags(Enum value, Enum[] values) { ulong bits = Convert.ToUInt64(value); List results = new List(); 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(); if (Convert.ToUInt64(value) != 0L) return results.Reverse(); if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L) return values.Take(1); return Enumerable.Empty(); } private static IEnumerable GetFlagValues(Type enumType) { ulong flag = 0x1; foreach (var value in Enum.GetValues(enumType).Cast()) { 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 flag per un tipo. Quindi i valori contenenti più bit sono esclusi.

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

Ecco una soluzione Linq al problema.

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

Tornando a questo punto alcuni anni dopo, con un po ‘di esperienza in più, la mia risposta definitiva ai soli valori a bit singolo, passando dal bit più basso al bit più alto, è una leggera variante della routine interiore di Jeff Mercado:

 public static IEnumerable GetUniqueFlags(this Enum flags) { ulong flag = 1; foreach (var value in Enum.GetValues(flags.GetType()).Cast()) { 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, poiché è molto più leggibile rispetto all'utilizzo di confronti bit a bit e la differenza di velocità è insignificante per qualsiasi cosa che farò. (È del tutto ansible che abbiano migliorato la velocità di HasFlag da quel momento in poi, per quel che ne so ... non l'ho testato.)

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

 public static IEnumerable GetUniqueFlags(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()) { ulong bits = Convert.ToUInt64(value); while (flag < bits) { flag <<= 1; } if (flag == bits && flags.HasFlag(value as Enum)) { yield return value; } } } 

Uscendo dal metodo di @ Greg, ma aggiungendo una nuova funzione da C # 7.3, il vincolo Enum :

 public static IEnumerable GetUniqueFlags(this Enum flags) where T : Enum // New constraint for C# 7.3 { foreach (Enum value in Enum.GetValues(flags.GetType())) if (flags.HasFlag(value)) yield return (T)value; } 

Il nuovo vincolo consente a questo di essere un metodo di estensione, senza dover eseguire il cast (int)(object)e , e posso usare il metodo HasFlag e HasFlag direttamente a T dal value .

C # 7.3 ha anche aggiunto vincoli a per delagates e unmanaged .

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

Dopo aver messo insieme alcune fonti diverse qui:
Poster precedente in SO QNA di questo thread
Codice Progetto Enum Flags Check Post
Utility Great Enum

L’ho creato così fammi sapere cosa ne pensi.
parametri:
bool checkZero : indica di consentire 0 come valore di flag. Di default input = 0 restituisce vuoto.
bool checkFlags : indica per verificare se Enum è decorato con l’attributo [Flags] .
PS. In questo momento non ho tempo per capire i checkCombinators = false alg che costringeranno a ignorare qualsiasi valore enum che sia una combinazione di bit.

  public static IEnumerable GetFlags(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.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 .GetValues()) { ulong valMask = Convert.ToUInt64(value); if ((setBits & valMask) == valMask) yield return value; } } } 

Questo fa uso dell’Herper Class Enum trovato qui che ho aggiornato per utilizzare il yield return per GetValues :

 public static class Enum { public static TEnum Parse(string value) { return (TEnum)Enum.Parse(typeof(TEnum), value); } public static IEnumerable GetValues() { foreach (object value in Enum.GetValues(typeof(TEnum))) yield return ((TEnum)value); } } 

Infine, ecco un esempio di utilizzo:

  private List GetCountTypes(CountType countTypes) { List cts = new List(); foreach (var ct in countTypes.GetFlags()) cts.Add(ct); return cts; } 

Non è necessario ripetere tutti i valori. basta controllare le tue bandiere specifiche in questo modo:

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

o (come ha detto pstrjds nei commenti) è ansible controllare per usarlo come:

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

Quello che ho fatto è stato cambiare il mio approccio, invece di digitare il parametro di input del metodo come tipo enum , l’ho digitato come una matrice del tipo enum ( MyEnum[] myEnums ), in questo modo ho semplicemente iterato attraverso l’array con un interruttore affermazione all’interno del ciclo.

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

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

Qualcuno saprebbe come migliorare ulteriormente in questo modo in modo che possa gestire il caso in cui tutti i flag nell’enumerazione sono impostati in modo super intelligente che potrebbe gestire tutti i tipi di enum sottostanti e il caso di Tutti = ~ 0 e Tutti = EnumValue1 | EnumValue2 | EnumValue3 | …

Puoi usare un Iterator dall’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 sentiti libero di 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.

Potrebbe essere anche il seguente codice:

 public static string GetEnumString(MyEnum inEnumValue) { StringBuilder sb = new StringBuilder(); foreach (MyEnum e in Enum.GetValues(typeof(MyEnum ))) { if ((e & inEnumValue) != 0) { sb.Append(e.ToString()); sb.Append(", "); } } return sb.ToString().Trim().TrimEnd(','); } 

Va dentro se solo quando il valore enum è contenuto nel valore

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 perfetto (boxe), ma fa il lavoro senza preavviso …

 ///  /// Return an enumerators of input flag(s) ///  ///  ///  public static IEnumerable GetFlags(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; } } } 

Uso:

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