在Flags枚举中迭代值?
如果我有一个variables持有一个标志枚举,我可以以某种方式遍历该特定variables中的位值? 或者我必须使用Enum.GetValues迭代整个枚举,并检查哪些设置?
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
内部生成的string,而不是返回标志。 你可以看reflection器中的代码,应该或多或less是等价的。 对于包含多个位的值的一般用例,适用性良好。
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()
获取一个types的所有单独的标志。 所以包含多个位的值被省略。
var value = Items.Bar | Items.Baz; value.GetFlags(); // Boo value.GetIndividualFlags(); // Bar, Baz
几年之后回过头来,有了更多的经验,我只能从最低位移动到最高位的最终答案是Jeff Mercado的内部例程:
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; } } }
这似乎工作,尽pipe我多年前反对,我在这里使用HasFlag,因为它比使用按位比较更清晰,速度的差异是微不足道的,我会做任何事情。 (无论如何,这是完全可能的,无论如何他们已经提高了HasFlags的速度,尽pipe我知道……我还没有testing过。)
这是一个Linq解决scheme的问题。
public static IEnumerable<Enum> GetFlags(this Enum e) { return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag); }
+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; } } }
你不需要迭代所有的值。 只是检查你的具体标志如下所示:
if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1) { //do something... }
或者(如pstrjds在评论中说),你可以检查使用它像:
if(myVar.HasFlag(FlagsEnum.Flag1)) { //do something... }
对上述答案不满意,虽然他们是开始。
在这里拼凑一些不同的来源之后:
在这个线程的SO QnA上一张海报
代码项目枚举标志检查发布
大的Enum <T>实用程序
我创造了这个,让我知道你的想法。
参数:
bool checkZero
:告诉它允许0
作为标志值。 默认情况下, input = 0
返回空。
bool checkFlags
:告诉它检查Enum
是否装饰有[Flags]
属性。
PS。 我现在没有时间去计算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; } } }
这使得在这里find的帮助类Enum <T>使用GetValues
yield return
:
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; }
我所做的是改变我的方法,而不是键入方法的input参数作为enum
types,我键入它的enum
types( MyEnum[] myEnums
)的数组,这样我只是迭代通过数组循环内的语句。
您可以使用Enum中的迭代器。 从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]; } } } }
它没有testing,所以如果我犯了一个错误,随时给我打电话。 (显然它不是可重入的。)你必须事先分配值,或将其分解成另一个使用enum.dayflag和enum.days的函数。 你可能可以去大纲的地方。
基于Greg的上面的回答,这也会照顾到你的枚举值为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; }
有人会知道如何进一步改进,以便它可以处理的情况下枚举中的所有标志都设置为一个超级聪明的方式,可以处理所有基础枚举types的情况下,所有=〜0和所有= EnumValue1 | EnumValue2 | EnumValue3 | …
你可以直接通过转换为int来完成,但是你会丢失types检查。 我认为最好的方法是使用类似于我的主张的东西。 它一直保持正确的types。 不需要转换。 这是不完美的,因为拳击会增加一点击中performance。
不完美(拳击),但它没有任何警告的工作…
/// <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()) { ... }