2010-11-13 81 views
103

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

+0

如果您能控制API,请避免使用位标志。它们很少是有用的优化。使用具有多个公共“布尔”字段的结构在语义上是等同的,但是您的代码要简单得多。如果您稍后需要,可以将字段更改为在内部操纵位字段的属性,从而封装优化。 – 2010-11-13 06:15:30

+0

我明白你在说什么,在很多情况下,这是有道理的,但在这种情况下,我会遇到与If建议一样的问题...我不得不为一个数组使用简单的foreach循环来编写If语句来处理十几个不同的布尔值。 (因为这是一个公共DLL的一部分,所以我不能做一些神秘的事情,比如只有一个布尔数组或者你有什么。) – RobinHood70 2010-11-13 06:32:57

+0

[通用扩展方法来查看一个枚举是否包含一个标志](http ://stackoverflow.com/questions/4108828/generic-extension-method-to-see-if-an-enum-contains-a-flag) – nawfal 2013-05-12 17:09:31

回答

21

说回这几年后,带着几分更多的经验,我只的单比特值最终的答案,从最低位移动到最高位,是微不足道的变种杰夫·梅尔卡多的内部程序:

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的速度,无论如何,对于我所知道的...我还没有测试过。)

+0

只有一个音符标志是初始化为一个int应该是一个ulong比如位,应该初始化为* 1ul * – forcewill 2015-02-27 14:24:28

+0

谢谢,我会解决的! (我只是去检查我的实际代码,并且我已经通过专门声明它是一个ulong来解决它。) – RobinHood70 2015-02-28 04:57:54

+2

这是我发现的唯一解决方案,似乎也不会因为如果你有一个值为零的标志,它应该表示“无”,其他答案GetFlag()方法将返回YourEnum.None作为其中一个标志,即使它实际上并不在您运行该方法的枚举中!我收到奇怪的重复日志条目,因为方法运行的次数比我预期的要多,只有一个非零枚举标志集。感谢您花时间更新并添加这个伟大的解决方案! – BrianHall 2015-05-21 20:12:51

36

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 
+0

我曾考虑做一个字符串拆分,但这可能比迭代整个枚举的位值要多得多。 – RobinHood70 2010-11-13 05:50:57

+0

不幸的是,这样做,你必须测试冗余值(如果不想要它们)。看到我的第二个例子,它会产生'Bar','Baz'和'Boo',而不仅仅是'Boo'。 – 2010-11-13 05:59:31

+0

有趣的是,你可以将Boo拿出来,尽管这部分是不必要的(事实上,这是一个非常糟糕的主意:))我正在做的事情。 – RobinHood70 2010-11-13 06:48:16

2

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

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-11-13 05:48:17

+0

谢谢你...我从来没有见过你说的,但它应该如此好... :) – 2010-11-13 05:49:42

+1

如果一个程序员不能理解按位操作,他们应该把它打包并找到新的职业。 – 2010-11-13 05:54:23

1

您可以使用一个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的函数。你可能能够在大纲的某个地方去。

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

这与我正在做的事情很接近,尽管我没有想过构建自己的枚举器(或者我知道我可以)。我对C#还是比较新的,所以这对我来说绝对是一种教育回应。谢谢! – RobinHood70 2010-11-13 06:59:25

+5

请注意,从.NET 4开始可以使用'HasFlag'。 – 2013-03-14 12:58:06

+3

这太棒了!但是你可以使它更简单易用。只要坚持这是一个扩展方法: 'Enum.GetValues(input.GetType())演员()。凡(input.HasFlag);' 然后,只需:'myEnum.GetFLags()':) – joshcomley 2013-08-16 12:28:20

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 here的,我更新使用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; 
    } 
+0

对不起,在几天内没有时间看这个项目。一旦我更好地了解了您的代码,我会尽快回复您。 – RobinHood70 2011-09-17 03:57:46

+3

只是提醒说代码中存在一个错误。 if(checkCombinators)语句的两个分支中的代码都是相同的。此外,也许不是一个错误,但意外的是,如果你有一个枚举值声明为0,它总是会返回集合中。看起来应该只在checkZero为true且没有设置其他标志的情况下返回。 – dhochee 2014-07-14 01:39:57

+0

@dhochee。我同意。或者代码非常好,但是争论很混乱。 – AFract 2017-12-12 18:27:57

2

我做了什么改变了我的方法,而不是键入方法的输入参数作为enum类型,我键入它的数组enum类型(MyEnum[] myEnums),这样我只需在循环内用switch语句迭代数组。

2

基于格雷格的回答,上面的这个也适用于你的枚举值为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; 
} 

有没有人知道如何在这甚至进一步提高,使其能够应对在枚举所有标志的,可以处理所有基础枚举类型和所有=的情况下,超级智​​能的方式设置的情况下〜0和All = EnumValue1 | EnumValue2 | EnumValue3 | ...

22

这是一个Linq解决方案。

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

为什么不在顶端? :)使用'.Where(v =>!Equals((int)(object)v,0)&& e.HasFlag(v));'如果您有一个零值来表示'None' – georgiosd 2017-05-23 18:32:59

-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()) 
    { 
     ... 
    } 
6

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

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