Iterar sobre valores em Flags Enum?

Se eu tenho uma variável segurando um enum flags, posso de alguma forma iterar sobre os valores de bits nessa variável específica? Ou eu tenho que usar Enum.GetValues ​​para iterar sobre todo o enum e verificar quais estão definidos?

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

Não existem methods AFAIK para obter cada componente. Aqui está uma maneira de obtê-los:

 [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 

Eu adaptei o que o Enum faz internamente para gerar a string para retornar as flags. Você pode olhar o código no refletor e deve ser mais ou menos equivalente. Funciona bem para casos de uso geral em que há valores que contêm vários bits.

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

O método de extensão GetIndividualFlags() obtém todos os sinalizadores individuais para um tipo. Portanto, valores contendo vários bits são omitidos.

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

Aqui está uma solução Linq para o problema.

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

Voltando a isso alguns anos mais tarde, com um pouco mais de experiência, minha resposta final para valores de bit único, passando do bit mais baixo para o bit mais alto, é uma ligeira variante da rotina interna de 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; } } } 

Parece funcionar, e apesar de minhas objeções de alguns anos atrás, eu uso HasFlag aqui, já que é muito mais legível do que usar comparações bit a bit e a diferença de velocidade é insignificante para qualquer coisa que eu esteja fazendo. (É bem possível que eles tenham melhorado a velocidade do HasFlags desde então, pelo que eu sei ... eu não testei.)

+1 para a resposta fornecida por @ RobinHood70. Descobri que uma versão genérica do método era conveniente para mim.

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

Saindo do método @ Greg, mas adicionando um novo recurso do C # 7.3, a restrição 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; } 

A nova restrição permite que isso seja um método de extensão, sem ter que passar por (int)(object)e , e eu posso usar o método HasFlag e converter diretamente para T partir do value .

C # 7.3 também adicionou restrições para delagates e unmanaged .

Não ficou satisfeito com as respostas acima, embora fossem o começo.

Depois de juntar algumas fonts diferentes aqui:
Poster anterior no segmento SO QnA
Código Projeto Enum Flags Check Post
Utilitário Great Enum

Eu criei isso então deixe-me saber o que você pensa.
parameters:
bool checkZero : diz para permitir 0 como um valor de flag. Por padrão, input = 0 retorna vazio.
bool checkFlags : informa para verificar se o Enum está decorado com o atributo [Flags] .
PS. Eu não tenho tempo agora para descobrir o checkCombinators = false alg que o forçará a ignorar quaisquer valores de enum que sejam combinações de bits.

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

Isso faz uso da class auxiliar Enum encontrada aqui que atualizei para usar o yield return para 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); } } 

Finalmente, aqui está um exemplo de usá-lo:

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

Você não precisa iterar todos os valores. basta verificar suas bandeiras específicas da seguinte forma:

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

ou (como pstrjds disse nos comentários) você pode verificar como usá-lo:

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

O que eu fiz foi mudar minha abordagem, em vez de digitar o parâmetro de input do método como o tipo enum , eu digitei como uma matriz do tipo enum ( MyEnum[] myEnums ), assim eu apenas percorrer o array com um switch declaração dentro do loop.

Baseando-se na resposta de Greg acima, isso também cuida do caso em que você tem um valor 0 no seu enum, como None = 0. Nesse caso, ele não deve iterar sobre esse valor.

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

Alguém poderia saber como melhorar isso ainda mais para que ele possa lidar com o caso em que todos os sinalizadores no enum são definidos de uma maneira super inteligente que poderia manipular todo o tipo de enum subjacente eo caso de All = ~ 0 e All = EnumValue1 | EnumValue2 | EnumValue3 | …

Você pode usar um Iterator do Enum. Começando pelo código do 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]; } } } } 

Não foi testado, por isso, se eu cometer um erro, não hesite em me chamar. (obviamente, não é re-entrante). Você teria que atribuir valor antecipadamente, ou dividi-lo em outra function que usa enum.dayflag e enum.days. Você pode ser capaz de ir a algum lugar com o contorno.

Poderia ser assim como o seguinte código:

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

Ele vai para dentro se apenas quando o valor enum estiver contido no valor

Você pode fazer isso diretamente convertendo para int, mas perderá a verificação de tipos. Eu acho que a melhor maneira é usar algo parecido com a minha proposição. Mantenha o tipo correto durante todo o tempo. Nenhuma conversão é necessária. Não é perfeito devido ao boxe, que irá adicionar um pouco de sucesso no desempenho.

Não perfeito (boxe), mas faz o trabalho sem aviso prévio …

 ///  /// 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()) { ... }