Se eu tiver uma variável segurando uma enumeração de sinalizadores, posso de alguma forma iterar sobre os valores de bits nessa variável específica? Ou preciso usar Enum.GetValues para iterar toda a enumeração e verificar quais estão definidas?
Se eu tiver uma variável segurando uma enumeração de sinalizadores, posso de alguma forma iterar sobre os valores de bits nessa variável específica? Ou preciso usar Enum.GetValues para iterar toda a enumeração e verificar quais estão definidas?
Respostas:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
está disponível no .NET 4 em diante.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
Em seguida, basta: myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
Aqui está uma solução Linq para o problema.
public static IEnumerable<Enum> GetFlags(this Enum e)
{
return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}
.Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));
se você tiver um valor zero para representarNone
Não existem métodos internos para obter cada componente, tanto quanto eu sei. Mas 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 Enum
faz internamente para gerar a string para retornar as bandeiras. Você pode ver o código no refletor e deve ser mais ou menos equivalente. Funciona bem para casos de uso geral em que existem valores que contêm vários bits.
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;
}
}
}
O método de extensão GetIndividualFlags()
obtém todos os sinalizadores individuais para um tipo. Portanto, valores contendo vários bits são deixados de fora.
var value = Items.Bar | Items.Baz;
value.GetFlags(); // Boo
value.GetIndividualFlags(); // Bar, Baz
Bar
, Baz
e Boo
não apenas Boo
.
Boo
(o valor retornado usando ToString()
). Eu o ajustei para permitir apenas sinalizadores individuais. Então, no meu exemplo, você pode obter Bar
e, em Baz
vez de Boo
.
Voltando a isso alguns anos depois, com um pouco mais de experiência, minha resposta final apenas para valores de bit único, passando do bit mais baixo para o mais alto, é uma ligeira variante da rotina interna de Jeff Mercado:
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;
}
}
}
Parece funcionar, e apesar das minhas objeções de alguns anos atrás, uso o HasFlag aqui, pois é 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. (É perfeitamente possível que eles tenham melhorado a velocidade do HasFlags desde então, pelo que sei ... não testei.)
yield return bits;
?
Saindo do método de Greg, mas adicionando um novo recurso do C # 7.3, a Enum
restrição:
public static IEnumerable<T> GetUniqueFlags<T>(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 este seja um método de extensão, sem ter que transmitir (int)(object)e
, e eu posso usar o HasFlag
método e transmitir diretamente para T
from value
.
O C # 7.3 também adicionou restrições para delagates e unmanaged
.
flags
parâmetro também fosse do tipo genérico T
, caso contrário, teria que especificar explicitamente o tipo de enum sempre que o chamar.
+1 para a resposta fornecida por @ RobinHood70. Eu descobri que uma versão genérica do método era conveniente para mim.
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;
}
}
}
EDIT E +1 para @AustinWBryan por trazer C # 7.3 para o espaço da solução.
public static IEnumerable<T> GetUniqueFlags<T>(this T flags) where T : Enum
{
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;
}
}
}
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 se está usando:
if(myVar.HasFlag(FlagsEnum.Flag1))
{
//do something...
}
O que fiz foi mudar minha abordagem, em vez de digitar o parâmetro de entrada do método como o enum
tipo, digitei-o como uma matriz do enum
tipo ( MyEnum[] myEnums
), dessa forma, apenas itero através da matriz com uma instrução switch dentro do loop.
Não ficou satisfeito com as respostas acima, embora tenham sido o começo.
Depois de reunir algumas fontes diferentes aqui:
Pôster anterior no tópico SO QnA
Code Projeto Enum Flags Check Post
Great Enum <T> Utility
Eu criei isso, então deixe-me saber o que você pensa.
Parâmetros::
bool checkZero
diz para permitir 0
como um valor de flag. Por padrão, input = 0
retorna vazio.
bool checkFlags
: diz para verificar se o Enum
item está decorado com o [Flags]
atributo
PS. Agora não tenho tempo para descobrir o checkCombinators = false
alg que o forçará a ignorar quaisquer valores de enumeração que são combinações de bits.
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;
}
}
}
Isso usa o Enum de classe auxiliar <T> encontrado aqui que eu atualizei para usar yield return
para 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);
}
}
Finalmente, aqui está um exemplo de como usá-lo:
private List<CountType> GetCountTypes(CountType countTypes)
{
List<CountType> cts = new List<CountType>();
foreach (var ct in countTypes.GetFlags())
cts.Add(ct);
return cts;
}
Com base na resposta de Greg acima, isso também trata do caso em que você tem um valor 0 na sua enumeração, como Nenhum = 0. Nesse caso, não deve iterar sobre esse valor.
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;
}
Alguém saberia como melhorar isso ainda mais para que ele possa lidar com o caso em que todos os sinalizadores na enum são definidos de uma maneira super inteligente que possa lidar com todo o tipo de enum subjacente e o caso de All = ~ 0 e All = EnumValue1 | EnumValue2 | EnumValue3 | ...
Você pode usar um iterador do Enum. A partir do código 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, sinta-se à vontade para me chamar. (obviamente, não é reentrante.) Você teria que atribuir valor antecipadamente ou dividi-lo em outra função que use enum.dayflag e enum.days. Você pode ir a algum lugar com o esboço.
Também pode ser 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 entra se apenas quando o valor de enum estiver contido no valor
Todas as respostas funcionam bem com sinalizadores simples. Você provavelmente entrará em problemas quando os sinalizadores forem combinados.
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
provavelmente precisará adicionar uma verificação para testar se o valor da enumeração é uma combinação. Você provavelmente precisaria de algo como postado aqui por Henk van Boeijen para atender às suas necessidades (você precisa rolar um pouco)
Método de extensão usando a nova restrição e genéricos Enum para impedir a transmissão:
public static class EnumExtensions
{
public static T[] GetFlags<T>(this T flagsEnumValue) where T : Enum
{
return Enum
.GetValues(typeof(T))
.Cast<T>()
.Where(e => flagsEnumValue.HasFlag(e))
.ToArray();
}
}
Você pode fazer isso diretamente convertendo para int, mas perderá a verificação de tipo. Eu acho que a melhor maneira é usar algo semelhante à minha proposição. Mantém o tipo adequado até o fim. Nenhuma conversão é necessária. Não é perfeito devido ao boxe, que adicionará um pequeno impacto no desempenho.
Não é perfeito (boxe), mas faz o trabalho sem aviso ...
/// <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;
}
}
}
Uso:
FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
foreach (FileAttributes fa in att.GetFlags())
{
...
}