如何遍历具有标志的Enum的值?


131

如果我有一个持有标志枚举的变量,我可以以某种方式迭代该特定变量中的位值吗?还是我必须使用Enum.GetValues遍历整个枚举并检查设置了哪些枚举?


如果您可以控制自己的API,请避免使用位标志。它们很少是有用的优化。使用具有多个公共“ bool”字段的结构在语义上是等效的,但是您的代码非常简单。而且,如果以后需要,可以将字段更改为在内部操作位字段的属性,从而封装优化。
杰伊·巴祖兹

2
我明白您在说什么,在很多情况下这是有道理的,但在这种情况下,我将遇到与If建议相同的问题...我不得不为许多不同的布尔值编写If语句在数组上使用简单的foreach循环的过程。(并且由于这是公共DLL的一部分,所以我无法执行诸如仅使用

10
“您的代码非常简单” –如果您要做的不仅仅是测试单个位,则完全相反……循环和设置操作实际上变得不可能,因为字段和属性不是一流的实体。
吉姆·巴尔特

2
@nawfal“有点”,我看到你在那儿做了什么
stannius 18/12/7

Answers:


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

7
请注意,该HasFlag功能可从.NET 4开始使用。
Andreas Grech

3
这很棒!但是,您可以使其更简单易用。只是将其作为扩展方法: Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag); 然后:myEnum.GetFLags():)
joshcomley

3
伟大的一线客气,但它仍然面临着选择多标记值(Boo)而不是仅获取单个标记值(Bar,Baz)的问题,就像上面Jeff的回答一样。

10
尼斯-不过要当心-例如,Items.Never永远不会包含Jeff的答案
Ilan

1
方法签名应为static IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers,2015年

48

这是该问题的Linq解决方案。

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

7
为什么这个不在顶部?:) .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));如果您使用零值来表示None
georgiosd

超级干净。我认为最好的解决方案。
瑞安·菲奥里尼

@georgiosd我猜性能不是那么好。(但对于大多数任务来说应该足够好)
AntiHeadshot

41

据我所知,没有任何内置方法可以获取每个组件。但是,您可以通过以下一种方式获取它们:

[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

我曾考虑过进行字符串拆分,但是这可能比仅迭代整个枚举的位值要多得多。

不幸的是,这样做,您必须测试冗余值(如果不想使用它们)。见我的第二个例子中,它会产生BarBazBoo不是只Boo
杰夫·梅卡多

有趣的是,您可以使Boo摆脱困境,尽管这对我正在做的事情是不必要的(实际上,这是一个非常糟糕的主意:))。

这看起来很有趣,但是如果我没有误会,它将为上述枚举返回Boo,在这里我只想枚举不是其他值组合的版本(即,是两个的幂的版本) 。可以容易做到吗?我一直在尝试,我想不出一种简单的方法就可以不借助FP数学来识别它。

@Robin:是的,原来会返回Boo(使用返回的值ToString())。我已对其进行了调整,以仅允许单个标志。因此,在我的示例中,您可以获取BarBaz代替Boo
杰夫·梅卡多

26

回到几年后,有了更多的经验,我对单比特值(从最低比特到最高比特)的最终回答是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应该是一个乌龙如位,应该被初始化为1UL
forcewill

谢谢,我会解决的!(我只是去检查了我的实际代码,并且已经通过明确声明它是ulong来解决了它。)

2
这是我发现的唯一解决方案,似乎也不会遭受以下事实的困扰:如果您有一个值为零的标志,该标志应表示“ None”,其他答案GetFlag()方法将返回YourEnum.None作为标志之一。即使实际上不在枚举中,您也要在上面运行该方法!我收到奇怪的重复日志条目,因为方法的运行时间比仅设置一个非零枚举标志时的预期时间还要多。感谢您抽出宝贵的时间来更新和添加这个出色的解决方案!
BrianH 2015年

yield return bits;
Jaider 2015年

1
我想要一个“ All”枚举变量,为此我分配了ulong.MaxValue,因此所有位都设置为“ 1”。但是您的代码会遇到无限循环,因为flag <位永远不会求值为true(flag循环为负,然后卡在0)。
Haighstrom

15

与@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方法并将其直接强制转换为Tfrom value

C#7.3还为滞后和限制添加了约束unmanaged


4
您可能还希望flags参数也为通用类型T,否则每次调用它时都必须显式指定枚举类型。

11

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

编辑 并为@AustinWBryan +1,以将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;
        }
    }
}


3

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

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

或(如pstrjds在评论中所述),您可以像下面这样检查使用情况:

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

5
如果您使用的是.Net 4.0,则可以使用扩展方法HasFlag来执行相同的操作:myVar.HasFlag(FlagsEnum.Flag1)
pstrjds 2010年

1
如果程序员不了解按位AND操作,则应该打包并找到新的职业。
Ed S.

2
@Ed:是的,但是当您再次阅读代码时,HasFlag会更好...(也许在几个月或几年之后)
TJ博士2010年

4
@Ed Swangren:这实际上是关于使代码更具可读性和更少冗长,这不一定是因为使用按位操作是“困难的”。
Jeff Mercado 2010年

2
HasFlag非常慢。尝试使用HasFlag与位掩码进行较大的循环,您会发现巨大的差异。

3

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


2

尽管只是开始,但对上述答案并不满意。

在这里拼凑了一些不同的来源之后:
此线程的SO QnA
代码项目Enum标志中的上一个发布者检查Post
Great Enum <T>实用程序

我创建了这个,所以让我知道您的想法。
参数::
bool checkZero告诉它允许0作为标志值。默认情况下input = 0返回空。
bool checkFlags:告诉它检查是否Enum[Flags]属性修饰了。
PS。我现在没有时间弄清楚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;
            }
        }

    }

这使得使用助手类的枚举<T>在这里找到我更新,以使用yield returnGetValues

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

抱歉,几天没来看看这个项目。更好地了解您的代码后,我会尽快与您联系。

4
请注意,该代码中存在错误。if(checkCombinators)语句的两个分支中的代码相同。同样,也许不是错误,但出乎意料的是,如果您将枚举值声明为0,则它​​将始终在集合中返回。似乎只应在checkZero为true且未设置其他标志的情况下返回。
dhochee 2014年

@dhochee。我同意。或代码很不错,但参数令人困惑。
AFract

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 | ...


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的函数。您也许可以将轮廓线放到某个地方。


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

仅当枚举值包含在值中时,它才会进入内部


0

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

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

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


0

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

-1

您可以通过将其转换为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())
    {
        ...
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.