Перебирать значения в перечислении флагов?
Если у меня есть переменная, содержащая перечисление флагов, могу ли я каким-то образом перебирать битовые значения в этой конкретной переменной? Или мне нужно использовать перечисление.GetValues для итерации по всему перечислению и проверки, какие из них установлены?
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 = falsealg, который заставит его игнорировать любые значения перечисления, которые являются комбинациями битов.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