简体   繁体   English

如何迭代具有标志的枚举的值?

[英]How to iterate over values of an Enum having flags?

If I have a variable holding a flags enum, can I somehow iterate over the single-bit values in that specific variable?如果我有一个包含标志枚举的变量,我可以以某种方式迭代该特定变量中的单位值吗? Or do I have to use Enum.GetValues to iterate over the entire enum and check which ones are set?还是我必须使用 Enum.GetValues 来遍历整个枚举并检查设置了哪些?

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

Here is a Linq solution to the problem.这是该问题的 Linq 解决方案。

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

There aren't any builtin methods to get each component as far as I know.据我所知,没有任何内置方法可以获取每个组件。 But here's one way you can get them:但这里有一种方法可以获得它们:

[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

I adapted what Enum does internally to generate the string to instead return the flags.我调整了Enum在内部所做的事情来生成字符串,而不是返回标志。 You can look at the code in reflector and should be more or less equivalent.您可以查看反射器中的代码,应该或多或少是等效的。 Works well for general use cases where there are values which contain multiple bits.适用于包含多个位的值的一般用例。

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

The extension method GetIndividualFlags() gets all the individual flags for a type.扩展方法GetIndividualFlags()获取类型的所有单个标志。 So values containing multiple bits are left out.所以包含多个位的值被排除在外。

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz

Coming back at this a few years later, with a bit more experience, my ultimate answer for single-bit values only, moving from lowest bit to highest bit, is a slight variant of Jeff Mercado's inner routine:几年后回到这个问题,有了更多的经验,我对单比特值的最终答案,从最低位到最高位,是杰夫梅尔卡多内部程序的一个轻微变体:

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

It seems to work, and despite my objections of some years ago, I use HasFlag here, since it's far more legible than using bitwise comparisons and the speed difference is insignificant for anything I'll be doing.它似乎有效,尽管我几年前反对,但我在这里使用 HasFlag,因为它比使用按位比较要清晰得多,而且速度差异对于我将要做的任何事情都无关紧要。 (It's entirely possible they've improved the speed of HasFlags since then anyway, for all I know...I haven't tested.) (从那时起,他们完全有可能提高了 HasFlags 的速度,据我所知......我还没有测试过。)

Going off of @Greg's method, but adding a new feature from C# 7.3, the Enum constraint:离开@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;
}

The new constraint allows this to be an extension method, without having to cast through (int)(object)e , and I can use the HasFlag method and cast directly to T from value .新的约束允许这是一个扩展方法,而不必通过(int)(object)e ,我可以使用HasFlag方法并直接从valueT

C# 7.3 also added constraints to for delegates and unmanaged . C# 7.3 还为委托和unmanaged添加了约束。

+1 for the answer provided by @RobinHood70. +1@RobinHood70 提供的答案。 I found that a generic version of the method was convenient for me.我发现该方法的通用版本对我来说很方便。

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

EDIT And +1 for @AustinWBryan for bringing C# 7.3 into the solution space.编辑并为@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;
        }
    }
}

我所做的是改变我的做法,而不是输入方法的输入参数enum类型,我输入它作为数组enum类型( MyEnum[] myEnums ),这样,我只是想迭代通过与开关阵列循环内的语句。

Extension method using the new Enum constraint and generics to prevent casting:使用新的 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();
    }
}

Building upon Greg's answer above, this also takes care of the case where you have a value 0 in your enum, such as None = 0. In which case, it should not iterate over that value.基于上面 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;
}

Would anyone know how to improve upon this even further so that it can handle the case where all flags in the enum are set in a super smart way that could handle all underlying enum type and the case of All = ~0 and All = EnumValue1 |有谁知道如何进一步改进它,以便它可以处理枚举中的所有标志都以超级智能的方式设置的情况,该方式可以处理所有底层枚举类型以及 All = ~0 和 All = EnumValue1 | 的情况。 EnumValue2 |枚举值2 | EnumValue3 |枚举值3 | ... ...

You dont need to iterate all values.您不需要迭代所有值。 just check your specific flags like so:只需检查您的特定标志,如下所示:

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

or (as pstrjds said in comments) you can check for use it like:或者(如pstrjds在评论中所说)您可以检查是否使用它,例如:

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

Continuing in my efforts to make the code shorter, this is my latest version of the routine.继续努力使代码更短,这是我的例程的最新版本。 (I'm the OP...long story.) As discussed previously, this ignores None and multi-bit values. (我是 OP...长话短说。)如前所述,这会忽略 None 和多位值。

Note that this uses an Enum constraint and a var pattern, so will require at least C# 7.3.请注意,这使用 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;
        }
    }
}

Wasn't satisfied with the answers above, although they were the start.对上面的答案并不满意,尽管它们是开始。

After piecing together some different sources here:在这里拼凑一些不同的来源后:
Previous poster in this thread's SO QnA 此线程的 SO QnA 中的上一张海报
Code Project Enum Flags Check Post 代码项目枚举标志检查帖子
Great Enum<T> Utility 伟大的 Enum<T> 实用程序

I created this so let me know what you think.我创建了这个所以让我知道你的想法。
Parameters:参数:
bool checkZero : tells it to allow 0 as a flag value. bool checkZero :告诉它允许0作为标志值。 By default input = 0 returns empty.默认情况下input = 0返回空。
bool checkFlags : tells it to check whether the Enum is decorated w/ the [Flags] attribute. bool checkFlags :告诉它检查Enum是否使用[Flags]属性进行装饰。
PS.附注。 I don't have time right now to figure out the checkCombinators = false alg which will force it to ignore any enum values which are combinations of bits.我现在没有时间弄清楚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;
            }
        }

    }

This makes use of the Helper Class Enum<T> found here that I updated to use yield return for GetValues :这利用了在这里找到的 Helper Class 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);
    }
}  

Finally, here's a example of using it:最后,这是一个使用它的例子:

    private List<CountType> GetCountTypes(CountType countTypes)
    {
        List<CountType> cts = new List<CountType>();

        foreach (var ct in countTypes.GetFlags())
            cts.Add(ct);

        return cts;
    }

You can use an Iterator from the Enum.您可以使用枚举中的迭代器。 Starting from the MSDN code:从 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];
            }
        }
    }
}

It's not tested, so if I made a mistake feel free to call me out.它没有经过测试,所以如果我犯了错误,请随时打电话给我。 (obviously it's not re-entrant.) You'd have to assign value beforehand, or break it out into another function that uses enum.dayflag and enum.days. (显然它不是可重入的。)您必须事先分配值,或者将其分解为另一个使用 enum.dayflag 和 enum.days 的函数。 You might be able to go somewhere with the outline.你也许可以用大纲去某个地方。

Here's yet another C# 7.3 solution using Linq这是另一个使用 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));
    }
}

It could be aswell as the following code:它也可以是以下代码:

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

It goes inside if only when the enum value is contained on the value只有当枚举值包含在值中时,它才会进入内部

All the answers work well with simple flags, you're probably going to get into issues when flags are combined.所有答案都适用于简单的标志,当标志组合时,您可能会遇到问题。

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

probably need to add a check to test if the enum value it self is a combination.可能需要添加一个检查来测试它本身的枚举值是否是一个组合。 You'd probably need something like posted here by Henk van Boeijen to cover your requirement (you need to scroll down a bit)您可能需要像Henk van Boeijen在这里发布的内容来满足您的要求(您需要向下滚动一点)

When it comes to performance, which is important in game development, all solutions given are terrible because of garbage allocation and slow speed.在游戏开发中很重要的性能方面,由于垃圾分配和速度慢,所有给出的解决方案都很糟糕。 This is a garbage free, over 100 times faster solution than accepted answer.这是一个无垃圾的解决方案,比公认的答案快 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;

You can do it directly by converting to int but you will loose type checking.您可以通过转换为 int 来直接执行此操作,但会丢失类型检查。 I think the best way is use something similar to my proposition.我认为最好的方法是使用类似于我的命题的东西。 It keep the proper type all the way.它始终保持正确的类型。 No conversion required.无需转换。 It is not perfect due to boxing which will add a little hit in performance.由于拳击,它并不完美,这会增加一些性能。

Not perfect (boxing), but it does the job with no warning...不完美(拳击),但它在没有警告的情况下完成了工作......

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

Usage:用法:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM