迭代Flags Enum中的值?

Rob*_*d70 123 c# enums enum-flags

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

Gre*_*reg 170

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

  • 很好 - 但要注意无人的 - 例如,Jeff的答案中的Items.None将始终包括在内 (8认同)
  • 请注意,`HasFlag`可从.NET 4开始提供. (7认同)
  • 这很棒!但是你可以使它更简单易用.只需将其作为扩展方法:`Enum.GetValues(input.GetType()).Cast <Enum>().Where(input.HasFlag);`那么只是:`myEnum.GetFLags()`:) (3认同)
  • 伟大的单行,josh,但它仍然遇到拾取多标志值(Boo)而不是单标志值(Bar,Baz)的问题,如Jeff的答案,如上所述. (3认同)
  • 方法签名应该是 `static IEnumerable&lt;Enum&gt; GetFlags(this Enum input)` (2认同)

agr*_*ton 42

这是问题的Linq解决方案.

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

  • 为什么不是这个?:)使用`.Where(v =>!Equals((int)(object)v,0)&& e.HasFlag(v));`如果你有一个零值来表示`None` (5认同)

Jef*_*ado 40

获取每个组件没有任何方法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
Run Code Online (Sandbox Code Playgroud)

我已经调整了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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

扩展方法GetIndividualFlags()获取类型的所有单独标志.因此省略了包含多个位的值.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz
Run Code Online (Sandbox Code Playgroud)


Rob*_*d70 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这是我发现的唯一解决方案似乎也没有遭受这样的事实:如果你有一个值为零的标志,它应该代表"无",其他答案GetFlag()方法将返回YourEnum.None作为其中一个标志即使它实际上不在枚举中你运行方法!我得到了奇怪的重复日志条目,因为当设置只有一个非零枚举标志时,方法运行的次数比我预期的要多.感谢您抽出宝贵时间更新并添加这个出色的解决方案! (2认同)

Aus*_*yan 12

关于@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;
}
Run Code Online (Sandbox Code Playgroud)

新约束允许这是一个扩展方法,而不必强制转换(int)(object)e,我可以使用该HasFlag方法并直接转换为Tfrom value.

C#7.3还为delagates添加了约束unmanaged.

  • 您可能也希望“flags”参数是泛型类型“T”,否则每次调用它时都必须显式指定枚举类型。 (7认同)

Wal*_*lly 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Sae*_*ini 7

使用新的 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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Rob*_*d70 5

我继续努力缩短代码,这是我的例程的最新版本。(我是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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)