Si tengo una variable que contiene una enumeración de banderas, ¿puedo iterar sobre los valores de bits en esa variable específica? ¿O tengo que usar Enum.GetValues para iterar sobre toda la enumeración y verificar cuáles están configurados?
Si tengo una variable que contiene una enumeración de banderas, ¿puedo iterar sobre los valores de bits en esa variable específica? ¿O tengo que usar Enum.GetValues para iterar sobre toda la enumeración y verificar cuáles están configurados?
Respuestas:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
está disponible desde .NET 4 en adelante.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
Entonces solo: myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
Aquí hay una solución de Linq al 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));
si tiene un valor cero para representarNone
No hay ningún método integrado para obtener cada componente hasta donde yo sé. Pero aquí hay una forma de obtenerlos:
[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
Adapte lo que Enum
hace internamente para generar la cadena para devolver las banderas. Puede mirar el código en reflector y debería ser más o menos equivalente. Funciona bien para casos de uso general donde hay valores que contienen múltiples 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;
}
}
}
El método de extensión GetIndividualFlags()
obtiene todas las banderas individuales para un tipo. Por lo tanto, los valores que contienen múltiples bits quedan fuera.
var value = Items.Bar | Items.Baz;
value.GetFlags(); // Boo
value.GetIndividualFlags(); // Bar, Baz
Bar
, Baz
y en Boo
lugar de solo Boo
.
Boo
(el valor devuelto usando ToString()
). Lo modifiqué para permitir solo banderas individuales. Entonces, en mi ejemplo, puedes obtener Bar
y en Baz
lugar de Boo
.
Volviendo a esto unos años más tarde, con un poco más de experiencia, mi respuesta final solo para valores de un solo bit, pasar del bit más bajo al más alto, es una ligera variante de la rutina 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, y a pesar de mis objeciones de hace algunos años, uso HasFlag aquí, ya que es mucho más legible que usar comparaciones bit a bit y la diferencia de velocidad es insignificante para cualquier cosa que haga. (Es completamente posible que hayan mejorado la velocidad de HasFlags desde entonces de todos modos, por lo que sé ... no lo he probado).
yield return bits;
?
Saliendo del método de @ Greg, pero agregando una nueva característica de C # 7.3, la Enum
restricción:
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;
}
La nueva restricción permite que este sea un método de extensión, sin tener que transmitir (int)(object)e
, y puedo usar el HasFlag
método y transmitir directamente T
desde value
.
C # 7.3 también agregó restricciones para retrasos y unmanaged
.
flags
parámetro fuera del tipo genérico T
, de lo contrario tendría que especificar explícitamente el tipo de enumeración cada vez que lo llame.
+1 por la respuesta proporcionada por @ RobinHood70. Descubrí que una versión genérica del método era conveniente para mí.
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;
}
}
}
EDITAR Y +1 para @AustinWBryan por traer C # 7.3 al espacio de solución.
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;
}
}
}
No necesita iterar todos los valores. simplemente verifique sus banderas específicas de esta manera:
if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1)
{
//do something...
}
o (como dijo pstrjds en los comentarios) puede verificar su uso como:
if(myVar.HasFlag(FlagsEnum.Flag1))
{
//do something...
}
Lo que hice fue cambiar mi enfoque, en lugar de escribir el parámetro de entrada del método como el enum
tipo, lo escribí como una matriz del enum
tipo ( MyEnum[] myEnums
), de esta manera solo itero a través de la matriz con una declaración de interruptor dentro del bucle.
No estaba satisfecho con las respuestas anteriores, aunque fueron el comienzo.
Después de juntar algunas fuentes diferentes aquí:
póster anterior en el
código SO QnA de este hilo Enum Flags Check Post
Great Enum <T> Utility
Creé esto, así que déjame saber lo que piensas.
Parámetros::
bool checkZero
le dice que permita 0
como valor de bandera. Por defecto input = 0
devuelve vacío.
bool checkFlags
: le dice que compruebe si Enum
está decorado con el [Flags]
atributo.
PD. No tengo tiempo en este momento para descubrir el checkCombinators = false
alg que lo obligará a ignorar cualquier valor de enumeración que sea una combinación 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;
}
}
}
Esto hace uso de Helper Class Enum <T> que se encuentra aquí y que actualicé 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, aquí hay un ejemplo de cómo usarlo:
private List<CountType> GetCountTypes(CountType countTypes)
{
List<CountType> cts = new List<CountType>();
foreach (var ct in countTypes.GetFlags())
cts.Add(ct);
return cts;
}
Sobre la base de la respuesta de Greg anterior, esto también se ocupa del caso en el que tiene un valor 0 en su enumeración, como Ninguno = 0. En cuyo caso, no debe iterar sobre ese 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;
}
¿Alguien sabría cómo mejorar esto aún más para que pueda manejar el caso en que todas las banderas en la enumeración se configuran de una manera súper inteligente que podría manejar todo el tipo de enumeración subyacente y el caso de All = ~ 0 y All = EnumValue1 | EnumValue2 | EnumValue3 | ...
Puedes usar un iterador de Enum. A partir del 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];
}
}
}
}
No está probado, así que si cometí un error, no dudes en llamarme. (obviamente no es reentrante). Debería asignar un valor de antemano o dividirlo en otra función que use enum.dayflag y enum.days. Es posible que pueda ir a algún lugar con el esquema.
Podría ser también el siguiente 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(',');
}
Entra solo si el valor de enumeración está contenido en el valor
Todas las respuestas funcionan bien con indicadores simples, probablemente se meterá en problemas cuando se combinen indicadores.
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
probablemente necesite agregar un cheque para probar si el valor de enumeración en sí mismo es una combinación. Probablemente necesite algo como lo publicado aquí por Henk van Boeijen para cubrir su requerimiento (debe desplazarse un poco hacia abajo)
Método de extensión que utiliza la nueva restricción Enum y genéricos para evitar la conversión:
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();
}
}
Puede hacerlo directamente mediante la conversión a int, pero perderá la verificación de tipos. Creo que la mejor manera es usar algo similar a mi propuesta. Mantiene el tipo correcto todo el camino. No se requiere conversión. No es perfecto debido al boxeo que agregará un poco de éxito en el rendimiento.
No es perfecto (boxeo), pero hace el trabajo sin advertencia ...
/// <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())
{
...
}