2012-04-11 16 views
11

Próbuję utworzyć pomocniczą metodę wymieniania nazw wszystkich bitów ustawionych w wartości wyliczenia (do celów rejestrowania). Chcę mieć metodę, która zwróci listę wszystkich wartości enum określonych w niektórych zmiennych. W moim przykładzieLista wszystkich nazw bitów z flagi Enum

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

karmię go 0x81 i powinien zapewnić mi z IEnumerable<HWResponse> zawierający {Ready, Error}.

Ponieważ nie znalazłem prostszego sposobu, próbowałem napisać poniższy kod, ale nie mogę go skompilować.

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

W tej wersji kodu kompilator skarży się, że nie może rzutować litery T na wyrażenie.

Co zrobiłem źle? Czy istnieje lepszy (prostszy) sposób to zrobić? Jak mogłem wykonać obsadę?

Również próbowałem napisać metodę jako

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

ale Enum jest ze specjalnego rodzaju, który zabrania „gdzie” składni (przy użyciu C# 4.0)

+1

To nie wygląda na to, że powinno to być oznaczeniem flagowym; kombinacje nie mają sensu. Czy coś może być "Pracujące" I "Gotowe" w tym samym czasie? –

+0

@DBM: To prawda, to tylko głupi przykład. – PPC

+0

@All: Dziękujemy za wspaniałe odpowiedzi. Wszystkie 3 są przydatne! – PPC

Odpowiedz

19

Oto prosty sposób, aby zapisać go przy użyciu 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

Świetne rozwiązanie: proste, czytelne i krótkie. Jedną wadą jednak: HasFlag (m) również wymienia stan "Brak" (0x0); ale jest to łatwe do przezwyciężenia przez stary styl binarny i – PPC

+1

Również HasFlag() wydaje się mieć duże problemy z wydajnością: zobacz inny wątek na http://stackoverflow.com/a/7164314/1121983 – PPC

+0

Czy to faktycznie działa? Kiedy próbuję rzucić 'var value = (T) Enum.GetValues ​​(typeof (T)) Cast () .FirstOrDefault (m => mask.HasFlag (m))' kompilator narzeka. – JobaDiniz

3

Jeśli pożądany efekt końcowy jest lista tytułów, po prostu zadzwoń mask.ToString().

Co byś zrobił, jeśli wyliczenia zostały zdefiniowane następująco:

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

Co do rozwiązywania błąd kompilatora, należy to zrobić:

Enum bit = (Enum)(object)curValueBit; 

Jon Skeet ma projekt o nazwie unconstrained melody że pozwala dodać ograniczenie enum po kompilacji przez przepisanie IL. Działa to, ponieważ CLR obsługuje takie ograniczenie, mimo że C# nie.

Inna myśl: To będzie bardziej efektywne, aby rzutować wartości zwracanej getValues ​​bezpośrednio do T[]:

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

Mask.ToString() jest rzeczywiście tym, co w końcu chciałem (choć wolę bardziej elastyczne rozwiązanie). Wygląda na to, że bardzo podobny kod jest zaimplementowany w strukturze, aby umożliwić taki wynik. Chciałbym to zobaczyć :) – PPC

+0

Nie jestem też fanem rozwiązania "ReadyAndWaiting": moje prawdziwe wyliczenie ma 14 flag i nie zamierzam realizować wszystkich możliwych etapów o tak długich nazwach :) – PPC

+0

Last ale nie mniej ważne: czy mógłbyś wyjaśnić nieco więcej obsady T []? Problemy z wydajnością? – PPC

1

Co jeśli po prostu zrobić coś takiego:

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

Jako as nie ma czas kompilacji. Kompilator tutaj "wierzy" w ciebie, mając nadzieję, że wiesz, co robisz, więc błąd kompilacji nie został podniesiony.

+0

Dlaczego nie 'foreach (Enum curValueBit in (T []) Enum.GetValues ​​(typeof (T)))'? Ponadto, dla przydatności jako rozszerzenia może być lepiej mieć to T maski zamiast maski Enum – Random832

+0

@ Random832: czy próbowałeś? – Tigran

+0

Instrukcja foreach działa, chociaż treść pętli wymaga modyfikacji i może mieć inną charakterystykę wydajności. Podejrzewam, że rzut do T [] zamiast .Cast jest zawsze lepszy. Martwiłbym się przypadkiem phooga, ze zdefiniowanymi wartościami, które mają więcej niż jedną flagę. Co robi w tym przypadku HasFlag? – Random832

2

budynek na Gabe's answer wymyśliłem to:

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

zorganizowałem rzeczy nieco inaczej, a mianowicie Włożyłem czek typu (tj .: weryfikacja, że ​​T jest rzeczywiście wyliczeniem) i uzyskanie wartości wyliczeniowych w konstruktorze statycznym, więc robi się to tylko raz (byłoby to poprawa wydajności).

Kolejna rzecz, dodałem opcjonalny parametr , dzięki czemu można zignorować typową "zero"/"Brak"/"Niezałożalna"/"Nieokreślona"/etc wartość wyliczenia.

Powiązane problemy