Список всех имен битов из флага Enum

Я пытаюсь создать вспомогательный метод для перечисления имен всех битов, заданных в значении Enum (для целей ведения журнала). Я хочу иметь метод, который возвращает список всех значений Enum, заданных в некоторых переменных. В моем примере

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

Я кормлю его 0x81, и он должен предоставить мне IEnumerable<HWResponse>, содержащий {Ready, Error}.

Поскольку я не нашел более простой способ, я попытался написать код ниже, но я не могу его скомпилировать.

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

В этой версии кода компилятор жалуется, что он не может отличать T от Enum.

Что я сделал неправильно? Есть ли лучший (более простой) способ сделать это? Как я могу сделать бросок?

Кроме того, я попытался написать метод как

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

но Enum имеет особый тип, который запрещает синтаксис "where" (с использованием С# 4.0)

Ответ 1

Вот простой способ записать его с помощью 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

Если ваш желаемый конечный результат представляет собой строковый список имен, просто вызовите mask.ToString().

Что бы вы сделали, если перечисление было определено следующим образом:

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

Что касается разрешения ошибки компилятора, это должно сделать это:

Enum bit = (Enum)(object)curValueBit;

У Jon Skeet есть проект под названием unconstrained melody, который позволяет вам добавить ограничение перечислимого числа после компиляции, переписав IL. Это работает, потому что CLR поддерживает такое ограничение, хотя С# этого не делает.

Еще одна мысль: будет эффективнее использовать возвращаемое значение GetValues ​​непосредственно в T[]:

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

Ответ 3

Основываясь на Ответ Gabe Я придумал это:

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

Я организовал вещи по-другому, а именно, я поставил проверку типа (то есть: подтверждение, что T действительно перечисление) и получение значений перечисления в статическом конструкторе, так что это делается только один раз (это было бы улучшение производительности).

Другое дело, я добавил необязательный параметр , чтобы вы могли игнорировать типичное значение "нуль" / "Нет" / "NotApplicable" / "Undefined" /etc для перечисления. p >

Ответ 4

Что делать, если что-то вроде этого:

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

Поскольку as не выполняет проверку времени компиляции. Компилятор здесь просто "верит" вам, надеясь, что вы знаете, что делаете, поэтому ошибка времени компиляции не возникает.