2010-11-13 24 views
103

Si tengo una variable que contenga una lista de banderas, ¿puedo de alguna manera iterar sobre los valores de bit 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?¿Cambiar los valores en Flags Enum?

+0

Si tiene control sobre su API, evite usar indicadores de bits. Raramente son una optimización útil. Usar una estructura con varios campos públicos 'bool' es semánticamente equivalente, pero su código es dramáticamente más simple. Y si lo necesita más tarde, puede cambiar los campos a propiedades que manipulan los campos de bits internamente, encapsulando la optimización. –

+0

Veo lo que dices, y en muchos casos tendría sentido, pero en este caso, tendría el mismo problema que la sugerencia If ...Tendría que escribir sentencias If para una docena de bools diferentes en lugar de usar un bucle foreach simple sobre una matriz. (Y dado que esto es parte de una DLL pública, no puedo hacer cosas arcanas como tener una matriz de bools o lo que sea). – RobinHood70

+0

posible duplicado de [Método de extensión genérico para ver si una enumeración contiene una bandera] (http : //stackoverflow.com/questions/4108828/generic-extension-method-to-see-if-an-enum-contains-a-flag) – nawfal

Respuesta

21

Volviendo al presente unos años más tarde, con una experiencia poco más, mi respuesta definitiva para sólo los valores de un solo bit, pasando de poco más bajo a más alto de bits, es una ligera variante del de Jeff Mercado rutina interna:

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 que funciona, ya pesar de mis objeciones de hace algunos años, yo uso HasFlag aquí, ya que es mucho más legible que usando comparaciones a nivel de bits y la diferencia de velocidad es insignificante para cualquier cosa que voy a hacer. (Es totalmente posible que hayan mejorado la velocidad de HasFlags desde entonces, de todos modos, por lo que sé ... no lo he probado.)

+0

Solo una nota es inicializado a un int debe ser un ulong como bits, debe ser inicializado como * 1ul * – forcewill

+0

Gracias, voy a arreglar eso! (Acabo de revisar mi código real y ya lo he solucionado al revés, declarando específicamente que es un ulong.) – RobinHood70

+2

Esta es la única solución que encontré que tampoco parece afectar el hecho de que si tener una bandera con valor cero, que debe representar "Ninguno", otras respuestas. Los métodos GetFlag() devolverán YourEnum.None como uno de los indicadores, incluso si no está realmente en la enumeración en la que ejecuta el método. Obtenía entradas de registro duplicadas extrañas porque los métodos se ejecutaban más veces de las que esperaba cuando solo tenían un indicador enum diferente de cero. ¡Gracias por tomarse el tiempo de actualizar y agregar esta gran solución! – BrianHall

36

No hay ningún método AFAIK para obtener cada componente. He aquí una manera que lo puedes conseguir:

[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 

he adaptado Enum lo hace internamente para generar la cadena de volver en lugar de las banderas. Puedes mirar el código en el 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() recibe 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 
+0

Consideré hacer una división de cadenas, pero eso probablemente es mucho más sobrecarga que solo iterar los valores de bit de la enumeración completa. – RobinHood70

+0

Desafortunadamente al hacerlo, tendría que probar los valores redundantes (si no los deseaba). Ver mi segundo ejemplo, daría 'Bar',' Baz' y 'Boo' en lugar de solo' Boo'. –

+0

Es interesante saber que puedes sacar a Boo de eso, aunque esa parte es innecesaria (y, de hecho, una muy mala idea :)) por lo que estoy haciendo. – RobinHood70

2

No necesita repetir los valores. acaba de ver sus banderas específicas de este modo:

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

o (como se ha dicho pstrjds en comentarios) puede comprobar si hay usarlo como:

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

Si está utilizando .Net 4.0 hay un método de extensión HasFlag que puede usar para hacer lo mismo: myVar.HasFlag (FlagsEnum.Flag1) – pstrjds

+0

gracias ... nunca he visto lo que dice, pero debería sé tan bueno ... :) –

+1

Si un programador no puede entender una operación Y a nivel de bit, debería empaquetarla y encontrar una nueva carrera. –

1

puede utilizar un iterador de la enumeración. 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 dude en llamarme. (obviamente no es reentrante). Tendría que asignar valor de antemano o dividirlo en otra función que use enum.dayflag y enum.days. Es posible que pueda ir a algún lado con el esquema.

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

Eso está cerca de lo que estoy haciendo, aunque no había pensado en construir mi propio enumerador (o sabía que podía). Todavía soy bastante nuevo en C#, así que definitivamente fue una respuesta educativa para mí. ¡Gracias! – RobinHood70

+5

Tenga en cuenta que 'HasFlag' está disponible desde .NET 4 en adelante. –

+3

Esto es genial! Pero puedes hacerlo aún más simple y fácil de usar. Sólo se adhieren esto como un método de extensión: 'Enum.GetValues ​​(input.GetType()) Reparto de () .Where (input.HasFlag);' A continuación, sólo:. 'MyEnum.GetFLags()' :) – joshcomley

3

No estaba satisfecho con las respuestas anteriores, aunque fueron el comienzo.

Después juntando algunas fuentes diferentes aquí:
Previous poster in this thread's SO QnA
Code Project Enum Flags Check Post
Great Enum<T> Utility

creé esto para que me haga saber lo que piensa.
Parámetros:
bool checkZero: le dice que permita 0 como valor de indicador. De forma predeterminada, input = 0 devuelve vacío.
bool checkFlags: le dice que verifique si el Enum está decorado con el atributo [Flags].
PS. No tengo tiempo ahora para descubrir el checkCombinators = false alg que lo forzará a ignorar cualquier valor enum 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 que el uso de la clase auxiliar Enum<T> found here que he actualizado a utilizar yield return de 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); 
    } 
} 

Por último, he aquí un ejemplo de su uso:

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

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

     return cts; 
    } 
+0

Lo sentimos, no hemos tenido tiempo de mirar este proyecto en varios días. Me pondré en contacto contigo una vez que hayamos visto mejor tu código. – RobinHood70

+3

Solo un aviso de que hay un error en ese código. El código en ambas ramas de la instrucción if (checkCombinators) es idéntico. Además, quizás no sea un error, sino inesperado, si tiene un valor enum declarado para 0, siempre será devuelto en la colección. Parece que solo debería devolverse si checkZero es verdadero y no hay otros indicadores establecidos. – dhochee

+0

@dhochee. Estoy de acuerdo. O el código es bastante bueno, pero los argumentos son confusos. – AFract

2

Lo que hice fue cambiar mi enfoque, en lugar de escribir el parámetro de entrada del método como el tipo enum, lo escribí como una matriz del enum tipo (MyEnum[] myEnums), de esta manera solo iteraré a través de la matriz con una declaración de cambio dentro del ciclo.

2

Basándose en la respuesta de Greg anterior, esto también se ocupa del caso en el que tiene un valor 0 en su enumeración, como None = 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 sabe cómo mejorar este aún más para que pueda manejar el caso en el que todas las banderas en la enumeración se establecen de una manera super inteligente que pudiera manejar todo tipo de enumeración subyacente y el caso de todas = ~ 0 y All = EnumValue1 | EnumValue2 | EnumValue3 | ...

22

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); 
} 
+0

¿Por qué no está esto en la parte superior? :) Use '.Where (v =>! Equals ((int) (object) v, 0) && e.HasFlag (v));' si tiene un valor cero para representar 'None' – georgiosd

-2

Puede hacerlo directamente convirtiendo a int, pero perderá la verificación de tipo. Creo que la mejor manera es usar algo similar a mi proposición. 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 previo 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()) 
    { 
     ... 
    } 
6

+1 por la respuesta proporcionada por @ RobinHood70. Descubrí que una versión genérica del método me resultaba conveniente.

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