Вопрос по c#, generics – Список всех имен битов из флага Enum

11

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

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

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

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

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

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

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

Также я попытался написать метод как

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

но Enum относится к особому типу, который запрещает «где» синтаксис (с использованием C # 4.0)

Это не похоже на то, что это должен быть флаг enum; комбинации не имеют смысла. Может ли что-то быть "работающим"? И & quot; Готово & quot; в то же время? Daniel Mann
@DBM: это правда, это просто глупый пример PPC
@All: Спасибо за ваши отличные ответы. Все 3 полезны! PPC

Ваш Ответ

4   ответа
2

Ответ Гейба Я придумал это:

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 действительно перечисление) и получение значений enum в статическом конструкторе, так что это делается только один раз (это будетperformance improvement).

Другое дело, я добавилoptional parameter so you can ignore типичный «ноль» / "Нет" / & quot; NotApplicable & quot; / & quot; Не определено & quot; / etc значение перечисления.

1

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 не проверяет время компиляции. Компилятор здесь просто "верит" Вы надеетесь, что знаете, что делаете, поэтомуcompile time error not raised.

@ Random832: ты пробовал это?
@ Random832: на самом деле я не использовалT, но использовалEnum and используемыйas оператор, чтобы заставить его скомпилировать и работать.
Оператор foreach работает, хотя тело цикла нуждается в модификации и может иметь разные характеристики производительности. Я подозреваю, что приведение к T [] вместо .Cast & lt; T & gt; всегда лучше Я должен быть обеспокоен случаем Фога с определенными значениями, которые имеют более одного флага. Что делает HasFlag в этом случае?
Почему бы и нетforeach(Enum curValueBit in (T[])Enum.GetValues(typeof (T)))? Кроме того, для полезности в качестве расширения может быть лучше использовать маску T вместо маски Enum.
@ Random832: точно, вам нужно сыгратьby the way, Я просто поставил собственный код с измененной одной строкой, то есть ..
3

мен, просто позвонитеmask.ToString().

Что бы вы сделали, если бы перечисление было определено так:

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

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

Enum bit = (Enum)(object)curValueBit;

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

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

foreach(T curValueBit in (T[])Enum.GetValues(typeof (T)))
@PPC, чтобы увидеть реализацию фреймворкаToString для флагов перечислений, скачатьILSpy и декомпилироватьSystem.Enum.InternalFlagsFormat.
Кроме того, я не фанат "ReadyAndWaiting" решение: мое реальное перечисление имеет 14 флагов, и я не собираюсь реализовывать все возможные этапы с такими длинными именами :) PPC
mask.ToString () действительно то, что я наконец-то хотел (хотя я бы предпочел более гибкое решение). Похоже, что очень похожий код реализован в рамках, чтобы позволить такой результат. Я бы хотел это увидеть :) PPC
@PPC Я не особенно фанатReadyAndWaiting либо, но если вы собираетесь иметь дело с перечислениями, которые вы не определили, возможно, вам придется иметь дело с такой возможностью. В противном случае это спорный вопрос. Что касается производительности, это не так плохо, как я думал: выражение(T[])Enum.GetValues(typeof (T))) дает вамT[]; Циклы foreach массива конвертируются компилятором C # в циклы for, которые более эффективны. Если вы позвонитеCast, вы в конечном итоге со ссылкой на массив какIEnumerable<T>вы используетеIEnumerator<T> объект, который немного менее эффективен.
И последнее, но не менее важное: не могли бы вы объяснить немного больше актеров в T []? Проблемы с производительностью? PPC
21

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>();
}
Это на самом деле работает? Когда я пытаюсь разыгратьvar value = (T)Enum.GetValues(typeof(T)).Cast<Enum>().FirstOrDefault(m => mask.HasFlag(m)) компилятор жалуется.
Отличное решение: простое, читаемое и короткое. Однако есть один недостаток: HasFlag (m) также перечисляет состояние «Нет»; (0x0); но это легко преодолеть с помощью бинарного файла старого стиля & amp; PPC
Также HasFlag (), кажется, имеет большие проблемы с производительностью: см. Другой поток наstackoverflow.com/a/7164314/1121983 PPC

Похожие вопросы