迭代Flags Enum中的值?

时间:2010-11-13 05:34:54

标签: c# enums

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

17 个答案:

答案 0 :(得分:166)

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

答案 1 :(得分:39)

没有任何方法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

答案 2 :(得分:35)

这是问题的Linq解决方案。

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

答案 3 :(得分:25)

几年后回到这里,有了更多的经验,我对单位值的最终答案,从最低位到最高位,是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的速度,我所知道的一切......我还没有测试过。)

答案 4 :(得分:11)

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

subscribe

答案 5 :(得分:6)

取消@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还为delagates和unmanaged添加了约束。

答案 6 :(得分:3)

对上述答案不满意,尽管它们是开始。

在这里拼凑了一些不同的来源:
Previous poster in this thread's SO QnA
Code Project Enum Flags Check Post
Great Enum<T> Utility

我创造了这个,所以让我知道你的想法 参数:
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;
            }
        }

    }

这会使用我更新的帮助程序类Enum<T> found hereyield 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;
    }

答案 7 :(得分:2)

您不需要迭代所有值。只需检查您的特定标志:

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

或(在评论中说pstrjds)您可以检查使用它:

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

答案 8 :(得分:2)

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

有没有人知道如何进一步改进这一点,以便它可以处理枚举中的所有标志都以超级智能方式设置的情况,该方式可以处理所有底层枚举类型和All = ~0和All的情况= EnumValue1 | EnumValue2 | EnumValue3 | ...

答案 9 :(得分:2)

我所做的是改变我的方法,而不是将方法的输入参数键入为enum类型,我将其键入为enum类型的数组(MyEnum[] myEnums)这样我只需在循环内部使用switch语句遍历数组。

答案 10 :(得分:1)

您可以使用Enum中的Iterator。从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];
            }
        }
    }
}

没有经过测试,所以如果我犯了错误,请随时给我打电话。 (显然它不是可重入的。)你必须事先赋值,或者将它分解成另一个使用enum.dayflag和enum.days的函数。你可能会去大纲的某个地方。

答案 11 :(得分:1)

使用新的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();
    }
}

答案 12 :(得分:1)

这是另一个使用 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));
    }
}

答案 13 :(得分:0)

可能还有以下代码:

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

只有在值

中包含枚举值时才会进入内部

答案 14 :(得分:0)

所有答案都可以通过简单的标志很好地使用,当组合标志时,您可能会遇到问题。

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

可能需要添加检查以测试其自身的枚举值是否为组合。 您可能需要类似Henk van Boeijen在此处发布的内容 满足您的要求(您需要向下滚动一点)

答案 15 :(得分:0)

我将继续努力使代码更短,这是我例程的最新版本。 (我是OP ...长话短说。)如上所述,它忽略了None和多位值。

请注意,这使用了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;
        }
    }
}

答案 16 :(得分:-2)

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