2012-04-11 29 views
11

Estoy tratando de hacer un método auxiliar para listar los nombres de todos los bits establecidos en un valor Enum (para propósitos de registro). Quiero tener un método que devuelva la lista de todos los valores Enum establecidos en algunas variables. En mi ejemploListar todos los nombres de bit de una marca Enum

[Flag] 
Enum HWResponse 
{ 
    None = 0x0, 
    Ready = 0x1, 
    Working = 0x2, 
    Error = 0x80, 
} 

que alimentarlo 0x81, y me debe proveer de un IEnumerable<HWResponse> contiene {Ready, Error}.

Como no encontré una manera más simple, traté de escribir el siguiente código, pero no puedo hacerlo compilar.

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{ 
    if (typeof(T).IsSubclassOf(typeof(Enum)) == false) 
    throw new ArgumentException(); 

    List<T> toreturn = new List<T>(100); 

    foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>()) 
    { 
    Enum bit = ((Enum) curValueBit); // Here is the error 

    if (mask.HasFlag(bit)) 
     toreturn.Add(curValueBit); 
    } 

    return toreturn; 
} 

En esta versión del código, el compilador se queja de que no puede convertir T en Enum.

¿Qué hice mal? ¿Hay una forma mejor (más simple) de hacer esto? ¿Cómo podría hacer el reparto?

Además, he intentado escribir el método como

public static IEnumerable<T> MaskToList<T>(Enum mask) where T:Enum 

pero Enum es de un tipo especial que prohíbe la sintaxis 'dónde' (Uso de C# 4.0)

+1

Esto no parece que deba ser una enumeración de la bandera; las combinaciones no tienen sentido. ¿Puede algo estar "trabajando" Y "listo" al mismo tiempo? –

+0

@DBM: Esto es cierto, es solo un ejemplo tonto – PPC

+0

@Todo: Gracias por sus excelentes respuestas. ¡Los 3 son útiles! – PPC

Respuesta

19

Aquí está una manera fácil de escribir usando LINQ:

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{ 
    if (typeof(T).IsSubclassOf(typeof(Enum)) == false) 
     throw new ArgumentException(); 

    return Enum.GetValues(typeof(T)) 
         .Cast<Enum>() 
         .Where(m => mask.HasFlag(m)) 
         .Cast<T>(); 
} 
+2

Gran solución: simple, legible y corta. Un inconveniente, sin embargo: HasFlag (m) también enumera el estado 'Ninguno' (0x0); pero es fácil de superar con un viejo estilo binario y – PPC

+1

También HasFlag() parece tener grandes problemas de rendimiento: ver otro hilo en http://stackoverflow.com/a/7164314/1121983 – PPC

+0

¿Esto realmente funciona? Cuando intento convertir 'var value = (T) Enum.GetValues ​​(typeof (T)). Cast () .FirstOrDefault (m => mask.HasFlag (m))' se queja el compilador. – JobaDiniz

3

Si su resultado final deseado es una lista de cadenas de nombres, simplemente llame al mask.ToString().

¿Qué haría usted si la enumeración se define así:

[Flags] 
enum State 
{ 
    Ready = 1, 
    Waiting = 2, 
    ReadyAndWaiting = 3 
} 

En cuanto a la solución del error de compilación, esto debe hacerlo:

Enum bit = (Enum)(object)curValueBit; 

Jon Skeet tiene un proyecto llamado unconstrained melody que le permite agregar la restricción enum, después de la compilación, reescribiendo el IL. Esto funciona porque la CLR admite dicha restricción, aunque C# no.

Otro pensamiento: Será más eficiente para convertir el valor devuelto de GetValues ​​directamente a T[]:

foreach(T curValueBit in (T[])Enum.GetValues(typeof (T))) 
+0

mask.ToString() es de hecho lo que finalmente quería (aunque preferiría tener una solución más flexible). Parece que se implementa un código muy similar en el marco para permitir tal resultado. Me gustaría verlo :) – PPC

+0

Además, no soy fanático de la solución 'ReadyAndWaiting': mi enum real tiene 14 banderas, y no voy a implementar todas las etapas posibles con nombres tan largos :) – PPC

+0

Último pero no menos importante, ¿podrías explicar un poco más el elenco a T []? ¿Problemas de desempeño? – PPC

1

¿Qué pasa si simplemente hacer algo como esto:

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{ 
if (typeof(T).IsSubclassOf(typeof(Enum)) == false) 
    throw new ArgumentException(); 

    List<T> toreturn = new List<T>(100); 

    foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>()) 
    { 
    Enum bit = (curValueBit as Enum); // The only difference is actually here, 
             // use "as", instead of (Enum) cast 

    if (mask.HasFlag(bit)) 
     toreturn.Add(curValueBit); 
    } 

    return toreturn; 
} 

A medida que el as no ha tiempo de compilación El compilador aquí simplemente "cree" en usted, con la esperanza de que sepa lo que está haciendo, por lo que no compila el error de tiempo.

+0

¿Por qué no 'foreach (Enum curValueBit in (T []) Enum.GetValues ​​(typeof (T)))'? Además, para su utilidad como extensión, puede ser mejor tenerlo en T máscara en lugar de Enum mask – Random832

+0

@ Random832: ¿lo intentó? – Tigran

+0

La instrucción foreach funciona, aunque el cuerpo del ciclo necesita modificaciones y puede tener diferentes características de rendimiento. Sospecho que el lanzamiento a T [] en lugar de .Cast es siempre mejor. Me preocuparía el caso de Phooog, con valores definidos que tienen más de un indicador. ¿Qué hace Haslag en este caso? – Random832

2

Basándose en Gabe's answer me ocurrió esto:

public static class EnumHelper<T> 
    where T : struct 
{ 
    // ReSharper disable StaticFieldInGenericType 
    private static readonly Enum[] Values; 
    // ReSharper restore StaticFieldInGenericType 
    private static readonly T DefaultValue; 

    static EnumHelper() 
    { 
     var type = typeof(T); 
     if (type.IsSubclassOf(typeof(Enum)) == false) 
     { 
      throw new ArgumentException(); 
     } 
     Values = Enum.GetValues(type).Cast<Enum>().ToArray(); 
     DefaultValue = default(T); 
    } 

    public static T[] MaskToList(Enum mask, bool ignoreDefault = true) 
    { 
     var q = Values.Where(mask.HasFlag); 
     if (ignoreDefault) 
     { 
      q = q.Where(v => !v.Equals(DefaultValue)); 
     } 
     return q.Cast<T>().ToArray(); 
    } 
} 

organicé las cosas un poco diferente, es decir, pongo la verificación de tipos (es decir, la verificación de que T es realmente una enumeración) y la obtención de los valores enum en el constructor estático, por lo que esto se hace solo una vez (esto sería mejora en el rendimiento).

Otra cosa, he añadido un parámetro opcional para que pueda ignorar el típico valor "cero"/"Ninguno"/"No aplicable"/"Indefinido"/etc de la enumeración.

Cuestiones relacionadas