2012-04-11 5 views
11

Sto cercando di creare un metodo di supporto per elencare i nomi di tutti i bit impostati in un valore Enum (per scopi di registrazione). Voglio avere un metodo che restituisca l'elenco di tutti i valori Enum impostati in alcune variabili. Nel mio esempioElenco di tutti i nomi bit di una bandiera Enum

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

ho alimentarlo 0x81, e dovrebbe fornire me con un IEnumerable<HWResponse> contenente {Ready, Error}.

Come non ho trovato un modo più semplice, ho provato a scrivere il codice qui sotto, ma non riesco a farlo compilare.

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

Su questa versione del codice, il compilatore si lamenta di non poter trasmettere T a Enum.

Cosa ho fatto di sbagliato? C'è un modo migliore (più semplice) per fare questo? Come potrei fare il cast?

Inoltre, ho provato a scrivere il metodo come

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

ma Enum è di un tipo speciale che vieta il 'dove' la sintassi (con C# 4.0)

+1

Questo non sembra dovrebbe essere un enum bandiera; le combinazioni non hanno senso. Qualcosa può essere "funzionante" e "pronto" allo stesso tempo? –

+0

@DBM: questo è vero, è solo un esempio stupido – PPC

+0

@All: Grazie per le tue grandi risposte. Tutti e 3 sono utili! – PPC

risposta

19

Ecco un modo semplice per scrivere utilizzando 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

Ottima soluzione: semplice, leggibile e breve. Un inconveniente però: HasFlag (m) elenca anche lo stato 'None' (0x0); ma è facile da superare con un binario vecchio stile e – PPC

+1

Anche HasFlag() sembra avere grossi problemi di prestazioni: vedere un altro thread su http://stackoverflow.com/a/7164314/1121983 – PPC

+0

Funziona davvero? Quando provo a lanciare 'var value = (T) Enum.GetValues ​​(typeof (T)). Cast () .FirstOrDefault (m => mask.HasFlag (m))' lamenta il compilatore. – JobaDiniz

3

Se il risultato finale desiderato è un lista di stringhe di nomi, basta chiamare il numero mask.ToString().

Cosa faresti se l'enumerazione sono stati definiti in questo modo:

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

Come per risolvere l'errore del compilatore, questo dovrebbe farlo:

Enum bit = (Enum)(object)curValueBit; 

Jon Skeet ha un progetto chiamato unconstrained melody che consente di aggiungere il vincolo enum, dopo la compilazione, riscrivendo l'IL. Questo funziona perché il CLR supporta tale vincolo, anche se C# non lo fa.

Un altro pensiero: Sarà più efficiente per il cast del valore di ritorno di GetValues ​​direttamente a T[]:

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

mask.ToString() è davvero ciò che volevo (anche se preferirei avere una soluzione più flessibile). Sembra che un codice molto simile sia implementato nel framework per consentire questo risultato. Mi piacerebbe vederlo :) – PPC

+0

Inoltre, non sono un fan della soluzione 'ReadyAndWaiting': la mia vera enumerazione ha 14 flag, e non ho intenzione di implementare tutte le fasi possibili con nomi così lunghi :) – PPC

+0

Ultimo ma non meno importante, potresti spiegare un po 'di più il cast a T []? Problemi di prestazione? – PPC

1

E se solo fare qualcosa di simile:

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

Come il as non ha compilare il controllo del tempo. Il compilatore qui semplicemente "ti crede", sperando che tu sappia cosa stai facendo, quindi l'errore di compilazione dello non è stato aumentato.

+0

Perché non 'foreach (Enum curValueBit in (T []) Enum.GetValues ​​(typeof (T)))'? Inoltre, per l'utilità come estensione, potrebbe essere meglio farlo prendere la maschera T al posto della maschera Enum – Random832

+0

@ Random832: l'hai provato? – Tigran

+0

L'istruzione foreach funziona, sebbene il corpo del ciclo debba essere modificato e potrebbe avere caratteristiche di prestazione diverse. Sospetto che il cast per T [] invece di.sia sempre migliore. Sarei preoccupato per il caso di phoog, con valori definiti che hanno più di un flag. Che cosa fa HasFlag in questo caso? – Random832

2

Sulla Gabe's answer sono arrivato fino a questo:

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

ho organizzato le cose un po 'diversamente, e cioè ho messo il controllo di tipo (es: la verifica che T sia davvero un'enumerazione) e l'ottenimento dei valori enum nel costruttore statico, quindi questo viene fatto una sola volta (questo sarebbe un miglioramento delle prestazioni ).

Un'altra cosa, ho aggiunto un parametro opzionale in modo da poter ignorare il valore tipico "zero"/"Nessuno"/"Non applicabile"/"Non definito"/ecc dell'enumerazione