Перебирать значения в перечислении флагов?



Если у меня есть переменная, содержащая перечисление флагов, могу ли я каким-то образом перебирать битовые значения в этой конкретной переменной? Или мне нужно использовать перечисление.GetValues для итерации по всему перечислению и проверки, какие из них установлены?

624   13  

13 ответов:

static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}

нет никаких методов AFAIK для получения каждого компонента. Вот один из способов, которым вы можете получить их:

[Flags]
enum Items
{
    None = 0x0,
    Foo  = 0x1,
    Bar  = 0x2,
    Baz  = 0x4,
    Boo  = 0x6,
}

var value = Items.Foo | Items.Bar;
var values = value.ToString()
                  .Split(new[] { ", " }, StringSplitOptions.None)
                  .Select(v => (Items)Enum.Parse(typeof(Items), v));

// This method will always end up with the most applicable values
value = Items.Bar | Items.Baz;
values = value.ToString()
              .Split(new[] { ", " }, StringSplitOptions.None)
              .Select(v => (Items)Enum.Parse(typeof(Items), v)); // Boo

я адаптировал что Enum делает внутренне генерировать строку, чтобы вместо этого вернуть флаги. Вы можете посмотреть код в отражателе и должны быть более или менее эквивалентны. Хорошо работает для общих случаев использования, когда есть значения, которые содержат несколько битов.

static class EnumExtensions
{
    public static IEnumerable<Enum> GetFlags(this Enum value)
    {
        return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray());
    }

    public static IEnumerable<Enum> GetIndividualFlags(this Enum value)
    {
        return GetFlags(value, GetFlagValues(value.GetType()).ToArray());
    }

    private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values)
    {
        ulong bits = Convert.ToUInt64(value);
        List<Enum> results = new List<Enum>();
        for (int i = values.Length - 1; i >= 0; i--)
        {
            ulong mask = Convert.ToUInt64(values[i]);
            if (i == 0 && mask == 0L)
                break;
            if ((bits & mask) == mask)
            {
                results.Add(values[i]);
                bits -= mask;
            }
        }
        if (bits != 0L)
            return Enumerable.Empty<Enum>();
        if (Convert.ToUInt64(value) != 0L)
            return results.Reverse<Enum>();
        if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L)
            return values.Take(1);
        return Enumerable.Empty<Enum>();
    }

    private static IEnumerable<Enum> GetFlagValues(Type enumType)
    {
        ulong flag = 0x1;
        foreach (var value in Enum.GetValues(enumType).Cast<Enum>())
        {
            ulong bits = Convert.ToUInt64(value);
            if (bits == 0L)
                //yield return value;
                continue; // skip the zero value
            while (flag < bits) flag <<= 1;
            if (flag == bits)
                yield return value;
        }
    }
}

метод расширения GetIndividualFlags() получает все отдельные флаги для типа. Так значения, содержащие несколько битов, не учитываются.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz

вот Linq решение проблемы.

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}
public static IEnumerable<Enum> GetUniqueFlags(this Enum flags)
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value))
        {
            yield return value;
        }
    }
}

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

+1 для ответа, предоставленного @RobinHood70. Я обнаружил, что общая версия метода была удобна для меня.

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("The generic type parameter must be an Enum.");

    if (flags.GetType() != typeof(T))
        throw new ArgumentException("The generic type parameter does not match the target type.");

    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

выход из метода @Greg, но добавление новой функции из C# 7.3,Enum ограничения:

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
    where T : Enum    // New constraint for C# 7.3
{
    foreach (Enum value in Enum.GetValues(flags.GetType()))
        if (flags.HasFlag(value))
            yield return (T)value;
}

новое ограничение позволяет этому быть методом расширения, без необходимости бросать через (int)(object)e, и я могу использовать HasFlag метод и приведение непосредственно к T С value.

в C# 7.3 также добавлены ограничения для послы и unmanaged.

не был удовлетворен ответами выше, хотя они были началом.

после объединения некоторых различных источников здесь:
предыдущий плакат в этой теме так QnA
Code Project Enum Flags Check Post
Большое Перечисление Утилита

я создал это так, дайте мне знать, что вы думаете.
Параметры:
bool checkZero: говорит ему разрешить 0 как значение флага. По умолчанию input = 0 возвращает пустой.
bool checkFlags: говорит ему, чтобы проверить, является ли Enum оформлен ж/.
ПС. У меня нет времени прямо сейчас, чтобы выяснить checkCombinators = false alg, который заставит его игнорировать любые значения перечисления, которые являются комбинациями битов.

    public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum input, bool checkZero = false, bool checkFlags = true, bool checkCombinators = true)
    {
        Type enumType = typeof(TEnum);
        if (!enumType.IsEnum)
            yield break;

        ulong setBits = Convert.ToUInt64(input);
        // if no flags are set, return empty
        if (!checkZero && (0 == setBits))
            yield break;

        // if it's not a flag enum, return empty
        if (checkFlags && !input.GetType().IsDefined(typeof(FlagsAttribute), false))
            yield break;

        if (checkCombinators)
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum<TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }
        else
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum <TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }

    }

это использует вспомогательный класс перечисление найдено здесь что я обновил использовать yield return на GetValues:

public static class Enum<TEnum>
{
    public static TEnum Parse(string value)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    public static IEnumerable<TEnum> GetValues()   
    {
        foreach (object value in Enum.GetValues(typeof(TEnum)))
            yield return ((TEnum)value);
    }
}  

наконец, вот пример его использования:

    private List<CountType> GetCountTypes(CountType countTypes)
    {
        List<CountType> cts = new List<CountType>();

        foreach (var ct in countTypes.GetFlags())
            cts.Add(ct);

        return cts;
    }

вам не нужно перебирать все значения. просто проверьте ваши конкретные флаги так:

if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1) 
{
   //do something...
}

или (как pstrjds сказал в комментариях) вы можете проверить его использование, как:

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}

что я сделал, так это изменил свой подход, вместо того, чтобы вводить входной параметр метода как enum тип, я набрал его как массив enum типа (MyEnum[] myEnums), таким образом, я просто повторяю массив с оператором switch внутри цикла.

основываясь на ответе Грега выше, это также заботится о случае, когда у вас есть значение 0 в вашем перечислении, например None = 0. В этом случае он не должен повторять это значение.

public static IEnumerable<Enum> ToEnumerable(this Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value) && Convert.ToInt64(value) != 0)
            yield return value;
}

кто-нибудь знает, как улучшить это еще больше, чтобы он мог обрабатывать случай, когда все флаги в перечислении установлены супер умным способом, который мог бы обрабатывать весь базовый тип перечисления и случай All = ~0 и All = EnumValue1 | EnumValue2 | EnumValue3 | ...

вы можете использовать итератор из перечисления. Начиная с кода MSDN:

public class DaysOfTheWeek : System.Collections.IEnumerable
{
    int[] dayflag = { 1, 2, 4, 8, 16, 32, 64 };
    string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
    public string value { get; set; }

    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < days.Length; i++)
        {
            if value >> i & 1 == dayflag[i] {
                yield return days[i];
            }
        }
    }
}

Это не проверено, так что если я сделал ошибку, не стесняйтесь вызвать меня. (очевидно, что это не повторный вход.) Вы должны были бы назначить значение заранее, или разбить его на другую функцию, которая использует перечисление.dayflag и enum.дни. Вы могли бы пойти куда-нибудь с планом.

это может быть также как и следующий код:

public static string GetEnumString(MyEnum inEnumValue)
{
    StringBuilder sb = new StringBuilder();

    foreach (MyEnum e in Enum.GetValues(typeof(MyEnum )))
    {
        if ((e & inEnumValue) != 0)
        {
           sb.Append(e.ToString());
           sb.Append(", ");
        }
    }

   return sb.ToString().Trim().TrimEnd(',');
}

Он входит внутрь, если только значение перечисления содержится в значении

Вы можете сделать это непосредственно путем преобразования в int, но вы потеряете проверять тип. Я думаю, что лучший способ-использовать что-то похожее на мое предложение. Он держит правильный тип полностью. Преобразование не требуется. Это не идеально из-за бокса, который добавит немного хита в исполнении.

не идеально (бокс), но он делает работу без предупреждения...

/// <summary>
/// Return an enumerators of input flag(s)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static IEnumerable<T> GetFlags<T>(this T input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
    {
        if ((int) (object) value != 0) // Just in case somebody has defined an enum with 0.
        {
            if (((Enum) (object) input).HasFlag(value))
                yield return (T) (object) value;
        }
    }
}

использование:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }

Comments

    Ничего не найдено.