如何迭代有标志的 Enum 的值?

如果我有一个包含标志枚举的变量,我是否可以以某种方式迭代该特定变量中的单位值?或者我必须使用 Enum。遍历整个枚举并检查设置了哪些枚举?

83792 次浏览

据我所知,没有任何内置的方法来获得每个组件。但是有一个办法可以让你得到它们:

[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

您不需要迭代所有值,只需检查您的特定标志,如下所示:

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

或者(正如 Pstrjds在评论中所说的)你可以检查它是否像下面这样使用:

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

您可以使用 Enum 中的 Iterator:

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 和枚举.days 的函数。你也许可以带着大纲去别的地方。

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

对上面的答案不满意,虽然它们只是开始。

在这里整理了一些不同的来源之后:
以前的海报在这个帖子的所以 QnA
代码项目枚举标志检查站
大枚举 < T > 实用工具

我创造了这个,所以让我知道你的想法。
参数:
告诉它允许 0作为标志值。默认情况下 input = 0返回空值。
bool checkFlags: 告诉它检查 Enum是否被修饰为 w/[Flags]属性。
附言。我现在没有时间去计算 checkCombinators = false算法,它会强迫它忽略任何枚举值,这些枚举值是位的组合。

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


}

这使用了我更新为对 GetValues使用 yield return的助手类 Enum < T > 在这里找到:

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

我所做的就是改变我的方法,而不是输入方法的输入参数作为 enum类型,我输入它作为一个 enum类型的数组(MyEnum[] myEnums) ,这样我只是通过循环中的 switch 语句迭代数组。

基于上面 Greg 的回答,这也解决了枚举中有值0的情况,比如 Nothing = 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 | ..。

下面是这个问题的 Linq 解决方案。

public static IEnumerable<Enum> GetFlags(this Enum e)
{
return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}

几年后,我带着更多的经验再次回到这个问题,我对于单位值的最终答案是从最低位到最高位,这是 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;
}
}
}

它看起来很有用,尽管我几年前就反对过,但我还是在这里使用了 HasFlag,因为它比按位比较更易读,而且速度差异对于我将要做的任何事情来说都是微不足道的。(他们完全有可能从那时起就提高了 HasFlags 的速度,就我所知... ... 我还没有测试过。)

您可以直接通过转换为 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())
{
...
}

对于@RobinHood70提供的答案,+ 1。我发现这个方法的通用版本对我来说很方便。

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

剪辑 + 1@AustinWBryan 将 C # 7.3带入解决方案空间。

public static IEnumerable<T> GetUniqueFlags<T>(this T flags) where T : Enum
{
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;
}
}
}


它也可以是以下代码:

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

只有当枚举值包含在该值上时,它才会进入内部

取消了@Greg 的方法,但是从 C # 7.3中添加了一个新特性,Enum约束:

public static IEnumerable<T> GetUniqueFlags<T>(this T 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方法并从 value直接强制转换到 T

C # 7.3还为委托和 unmanaged添加了约束。

对于简单的标志,所有的答案都能很好地工作,当标志组合在一起时,你可能会遇到问题。

[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}

可能需要添加一个检查来测试枚举值本身是否是一个组合。 你可能需要像这样的东西张贴在这里的 Henk van Boeijen 以满足您的需求(您需要向下滚动一点)

使用新的 Enum 约束和泛型防止强制转换的扩展方法:

public static class EnumExtensions
{
public static T[] GetFlags<T>(this T flagsEnumValue) where T : Enum
{
return Enum
.GetValues(typeof(T))
.Cast<T>()
.Where(e => flagsEnumValue.HasFlag(e))
.ToArray();
}
}

继续努力使代码更短,这是我的最新版本的例程。(我是 OP... 说来话长)正如前面所讨论的,这将忽略 Nothing 和多位值。

注意,这使用了 Enum 约束和 var 模式,因此至少需要 C # 7.3。

public static IEnumerable<T> GetUniqueFlags<T>(this T value)
where T : Enum
{
var valueLong = Convert.ToUInt64(value, CultureInfo.InvariantCulture);
foreach (var enumValue in value.GetType().GetEnumValues())
{
if (
enumValue is T flag // cast enumValue to T
&& Convert.ToUInt64(flag, CultureInfo.InvariantCulture) is var bitValue // convert flag to ulong
&& (bitValue & (bitValue - 1)) == 0 // is this a single-bit value?
&& (valueLong & bitValue) != 0 // is the bit set?
)
{
yield return flag;
}
}
}

下面是另一个使用 Linq 的 C # 7.3解决方案

public static class FlagEnumExtensions
{
public static IEnumerable<T> GetFlags<T>(this T en) where T : struct, Enum
{
return Enum.GetValues<T>().Where(member => en.HasFlag(member));
}
}

当涉及到性能,这是游戏开发的重要性,所有的解决方案都是可怕的,因为垃圾分配和缓慢的速度。这是一个垃圾自由,超过100倍的速度比接受的答案解决方案。

[Flags]
public enum PersonalTraits : short
{
None = 1 << 0,
Strength = 1 << 1,
Agility = 1 << 2,
Attack = 1 << 3,
Defence = 1 << 4,
Vitality = 1 << 5,
Stamina = 1 << 6,
Accuracy = 1 << 7,
Perception = 1 << 8,
Charisma = 1 << 9,
}
PersonalTraits athlete = PersonalTraits.Stamina | PersonalTraits.Strength;


for (short i = 0, value = 0; value <= (short)PersonalTraits.Charisma; i++, value = (short)(1 << i))
if (((short)athlete & value) != 0)
yield return (PersonalTraits)value;