【问题标题】:How do I get all possible combinations of an enum ( Flags )如何获得枚举的所有可能组合(标志)
【发布时间】:2011-05-24 21:17:41
【问题描述】:
[Flags]
public enum MyEnum
{
    None = 0,
    Setting1 = (1 << 1),
    Setting2 = (1 << 2),
    Setting3 = (1 << 3),
    Setting4 = (1 << 4),
}

我需要能够以某种方式遍历每个可能的设置并将设置组合传递给函数。可悲的是我一直无法弄清楚如何做到这一点

【问题讨论】:

  • 这将获得所有设置,但不是所有组合。请记住这是标志,因此您可以同时拥有 Setting1 和 Setting2。
  • 这不只是你如何编写循环的问题......
  • @Ryan,这不是骗人的,因为其他问题不包括 标志 的组合,但它是一个很好的链接

标签: c# enum-flags


【解决方案1】:

未经测试,使用风险自负,但应该足够笼统地解决问题。 System.Enum 不是一个有效的限制,因为从技术上讲,C# 只允许在class 中继承/使用class 进行继承,对于EnumValueType,后端会绕过此限制。对丑陋的演员阵容感到抱歉。它也不是非常有效,但除非您针对动态生成的类型运行它,否则每次执行只需要执行一次(或者如果保存一次)。

public static List<T> GetAllEnums<T>()
    where T : struct
    // With C# 7.3 where T : Enum works
{
    // Unneeded if you add T : Enum
    if (typeof(T).BaseType != typeof(Enum)) throw new ArgumentException("T must be an Enum type");

    // The return type of Enum.GetValues is Array but it is effectively int[] per docs
    // This bit converts to int[]
    var values = Enum.GetValues(typeof(T)).Cast<int>().ToArray();

    if (!typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false).Any())
    {
        // We don't have flags so just return the result of GetValues
        return values;
    }

    var valuesInverted = values.Select(v => ~v).ToArray();
    int max = 0;
    for (int i = 0; i < values.Length; i++)
    {
        max |= values[i];
    }

    var result = new List<T>();
    for (int i = 0; i <= max; i++)
    {
        int unaccountedBits = i;
        for (int j = 0; j < valuesInverted.Length; j++)
        {
            // This step removes each flag that is set in one of the Enums thus ensuring that an Enum with missing bits won't be passed an int that has those bits set
            unaccountedBits &= valuesInverted[j];
            if (unaccountedBits == 0)
            {
                result.Add((T)(object)i);
                break;
            }
        }
    }

    //Check for zero
    try
    {
        if (string.IsNullOrEmpty(Enum.GetName(typeof(T), (T)(object)0)))
        {
            result.Remove((T)(object)0);
        }
    }
    catch
    {
        result.Remove((T)(object)0);
    }

    return result;
}

这通过获取所有值并将它们组合在一起而不是求和来实现,以防包含合数。然后它将每个整数取到最大值,并用每个 Flag 的倒数对其进行掩码,这导致有效位变为 0,从而使我们能够识别那些不可能的位。

最后的检查是从枚举中丢失零。如果您可以始终在结果中包含零枚举,则可以将其删除。

当给定一个包含 2,4,6,32,34,16384 的枚举时,给出 15 的预期结果。

【讨论】:

  • 对不起,这里是个业余爱好者,但是你怎么称呼这个函数呢?
  • 枚举类型作为类型参数传递,在您的示例中:GetAllEnums&lt;MyEnum&gt;() 请注意,您需要将其放在一个类中,并可能以该类名开头,例如,如果它在一个名为 Utility 的静态类中,然后是 Utility.GetAllEnums&lt;MyEnum&gt;()
  • C# 7.3 中的新功能:where T : System.Enumgithub.com/dotnet/docs/issues/3964
【解决方案2】:

既然是标记枚举,何不干脆:

  1. 获取枚举的最高值。
  2. 计算组合的数量,即上限。
  3. 迭代每个组合,即从 0 循环到上限。

示例如下所示

var highestEnum = Enum.GetValues(typeof(MyEnum)).Cast<int>().Max();
var upperBound = highestEnum * 2;    
for (int i = 0; i < upperBound; i++)
{
    Console.WriteLine(((MyEnum)i).ToString());
}

【讨论】:

  • 这很好用但需要:.Cast).Max();谢谢!
  • 标志枚举不需要考虑每一位。有关示例,请参见 System.IO.FileAttributes。
  • 切换到 i = 1 作为起点,因为 0 不是有效的标志枚举值。
【解决方案3】:

这是一个特定于您的代码示例的解决方案,使用简单的 for 循环(不要使用,请参阅下面的更新)

int max = (int)(MyEnum.Setting1 | MyEnum.Setting2 | MyEnum.Setting3 | MyEnum.Setting4);
for (int i = 0; i <= max; i++)
{
    var value = (MyEnum)i;
    SomeOtherFunction(value);
}

更新:这是一个通用方法,它将返回所有可能的组合。并感谢 @David Yaw 提出使用队列来构建每个组合的想法。

IEnumerable<T> AllCombinations<T>() where T : struct
{
    // Constuct a function for OR-ing together two enums
    Type type = typeof(T);
    var param1 = Expression.Parameter(type);
    var param2 = Expression.Parameter(type);
    var orFunction = Expression.Lambda<Func<T, T, T>>(
        Expression.Convert(
            Expression.Or(
                Expression.Convert(param1, type.GetEnumUnderlyingType()),
                Expression.Convert(param2, type.GetEnumUnderlyingType())),
            type), param1, param2).Compile();

    var initalValues = (T[])Enum.GetValues(type);
    var discoveredCombinations = new HashSet<T>(initalValues);
    var queue = new Queue<T>(initalValues);

    // Try OR-ing every inital value to each value in the queue
    while (queue.Count > 0)
    {
        T a = queue.Dequeue();
        foreach (T b in initalValues)
        {
            T combo = orFunction(a, b);
            if (discoveredCombinations.Add(combo))
                queue.Enqueue(combo);
        }
    }

    return discoveredCombinations;
}

【讨论】:

  • 这在给定的情况下有效,但如果有一些 2 的幂不是小于最大成员的成员,则不会工作。
  • MyEnum.Setting4 不够高,您需要 setting1 &2&3&4 对吗?
  • @dnolan - 谢谢你指出这一点,我有多密集。我已经确定了答案。
  • @Ian - 没有争论。一个通用的解决方案会更复杂。
  • @Greg 没问题,正如@Ian 指出的那样,如果假设所有标志都在该范围内使用,则此解决方案将起作用,如果缺少一个标志,它将崩溃。
【解决方案4】:
    public IEnumerable<TEnum> AllCombinations<TEnum>() where TEnum : struct
    {
        Type enumType = typeof (TEnum);
        if (!enumType.IsEnum)
            throw new ArgumentException(string.Format("The type {0} does not represent an enumeration.", enumType), "TEnum");

        if (enumType.GetCustomAttributes(typeof (FlagsAttribute), true).Length > 0) //Has Flags attribute
        {
            var allCombinations = new HashSet<TEnum>();

            var underlyingType = Enum.GetUnderlyingType(enumType);
            if (underlyingType == typeof (sbyte) || underlyingType == typeof (short) || underlyingType == typeof (int) || underlyingType == typeof (long))
            {
                long[] enumValues = Array.ConvertAll((TEnum[]) Enum.GetValues(enumType), value => Convert.ToInt64(value));
                for (int i = 0; i < enumValues.Length; i++)
                    FillCombinationsRecursive(enumValues[i], i + 1, enumValues, allCombinations);
            }
            else if (underlyingType == typeof (byte) || underlyingType == typeof (ushort) || underlyingType == typeof (uint) || underlyingType == typeof (ulong))
            {
                ulong[] enumValues = Array.ConvertAll((TEnum[]) Enum.GetValues(enumType), value => Convert.ToUInt64(value));
                for (int i = 0; i < enumValues.Length; i++)
                    FillCombinationsRecursive(enumValues[i], i + 1, enumValues, allCombinations);
            }
            return allCombinations;
        }
        //No Flags attribute
        return (TEnum[]) Enum.GetValues(enumType);
    }

    private void FillCombinationsRecursive<TEnum>(long combination, int start, long[] initialValues, HashSet<TEnum> combinations) where TEnum : struct
    {
        combinations.Add((TEnum)Enum.ToObject(typeof(TEnum), combination));
        if (combination == 0)
            return;

        for (int i = start; i < initialValues.Length; i++)
        {
            var nextCombination = combination | initialValues[i];
            FillCombinationsRecursive(nextCombination, i + 1, initialValues, combinations);
        }
    }

    private void FillCombinationsRecursive<TEnum>(ulong combination, int start, ulong[] initialValues, HashSet<TEnum> combinations) where TEnum : struct
    {
        combinations.Add((TEnum)Enum.ToObject(typeof(TEnum), combination));
        if (combination == 0)
            return;

        for (int i = start; i < initialValues.Length; i++)
        {
            var nextCombination = combination | initialValues[i];
            FillCombinationsRecursive(nextCombination, i + 1, initialValues, combinations);
        }
    }

【讨论】:

  • 请添加一些文字来解释代码。一定要那么长吗?
  • 嗯,在第一个方法AllCombinations中,首先我检查类型是否为Enum。然后我想确定它有Flags属性。如果是这样,我分析底层类型,它可以是任何整数类型。如果它是有符号整数之一,我会转换为最大的有符号整数(长整数),以免丢失数据。如果它是无符号整数之一,我将转换为最大的无符号整数(ulong)。之后,我只调用完成这项工作的递归函数。第二种和第三种方法几乎相同,只是输入参数的类型不同。由于按位 OR 运算符,我无法使用泛型类型。
【解决方案5】:

首先,获取所有单个值的列表。因为你有 5 个值,那就是 (1 &lt;&lt; 5) = 32 个组合,所以从 1 迭代到 31。(不要从零开始,这意味着不包含任何枚举值。)迭代时,检查位数字,迭代变量中的每一位都意味着包含该枚举值。将结果放入 HashSet 中,这样就不会有重复项,因为包含 'None' 值不会改变结果枚举。

List<MyEnum> allValues = new List<MyEnum>(Enum.Getvalues(typeof(MyEnum)));
HashSet<MyEnum> allCombos = new Hashset<MyEnum>();

for(int i = 1; i < (1<<allValues.Count); i++)
{
    MyEnum working = (MyEnum)0;
    int index = 0;
    int checker = i;
    while(checker != 0)
    {
        if(checker & 0x01 == 0x01) working |= allValues[index];
        checker = checker >> 1;
        index++;
    }
    allCombos.Add(working);
}

【讨论】:

    【解决方案6】:

    当我向枚举添加新成员时,我通常不想更新每个表示枚举最大值的变量。 例如,我不喜欢 Greg 提出的陈述:

    int max = (int)(MyEnum.Setting1 | MyEnum.Setting2 | ... | MyEnum.SettingN);
    

    考虑一下,当您的解决方案中散布着这些变量中的几个并且您决定修改您的枚举时。这肯定不是理想的情况。

    我会提前承认我的代码速度较慢,但​​是在修改枚举后它会自动正确,并且我努力以这种健壮的方式编写代码。我愿意为此付出一些计算代价,反正 C# 就是这样。我建议:

    public static IEnumerable<T> GetAllValues<T>() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Generic argument is not an enumeration type");
        int maxEnumValue = (1 << Enum.GetValues(typeof(T)).Length) - 1;
        return Enumerable.Range(0, maxEnumValue).Cast<T>();
    }
    

    这假设枚举包含 2 到某个幂(包括 0)的所有幂的成员,就像通常使用的标志枚举一样。

    【讨论】:

      【解决方案7】:

      我可能有点晚了我想离开我的解决方案,其中还包括值加上组合的可能文本,形式为 ("V1 | V2", "V1 | V2 | V3",等)。

      我采用了上面提出的解决方案的某些方面,因此感谢所有发布先前答案的人:D。

      注意:仅适用于设置为基数 2 组合的 Enum。

      public static Dictionary<int,string> GetCombinations( this Enum enu)
          {
              var fields = enu.GetType()
                              .GetFields()
                              .Where(f => f.Name != "value__")
                              .DistinctBy(f=> Convert.ToInt32(f.GetRawConstantValue()));
      
              var result = fields.ToDictionary(f=>Convert.ToInt32(f.GetRawConstantValue()), f => f.Name);
      
              int max = Enum.GetValues(enu.GetType()).Cast<int>().Max();
              int upperBound = max * 2;
      
              for (int i = 0 ; i <= upperBound ; i += 2)
              {
                  string s = Convert.ToString(i, 2).PadLeft(Math.Abs(i-max),'0');
                  Boolean[] bits = s.Select(chs => chs == '1' ? true : false)
                                   .Reverse()
                                   .ToArray();
      
                  if (!result.ContainsKey(i))
                  {
                      var newComb = string.Empty;
                      for (int j = 1; j < bits.Count(); j++)
                      {
                          var idx = 1 << j;
                          if (bits[j] && result.ContainsKey(idx))
                          {
                              newComb = newComb + result[idx] + " | ";
                          }
                      }                                      
                      newComb = newComb.Trim(new char[] { ' ', '|' });
                      if (!result.ContainsValue(newComb) && !string.IsNullOrEmpty(newComb))
                      {
                          result.Add(i, newComb);
                      }                    
                  }
              }
              return result;
          }
      

      【讨论】:

      • 通过重新格式化现有答案,它增加了什么价值?
      • 之前的答案只返回数字作为组合的结果,我希望也返回形成组合的显示值。
      【解决方案8】:

      此版本结束:

      没有检查:

      public IEnumerable<T> AllCombinations<T>() where T : struct
      {
          var type = typeof(T);
          for (var combination = 0; combination < Enum.GetValues(type).Cast<int>().Max()*2; combination++)
          {
              yield return (T)Enum.ToObject(type, combination);
          }
      }
      

      通过一些检查

      public IEnumerable<T> AllCombinations<T>() where T : struct
      {
          var type = typeof(T);
          if (!type.IsEnum)
          {
              throw new ArgumentException($"Type parameter '{nameof(T)}' must be an Enum type.");
          }
      
          for (var combination = 0; combination < Enum.GetValues(type).Cast<int>().Max()*2; combination++)
          {
              var result = (T)Enum.ToObject(type, combination);
      
              // Optional check for legal combination.
              // (and is not necessary if all flag a ascending exponent of 2 like 2, 4, 8...
              if (result.ToString() == combination.ToString() && combination != 0)
              {
                  continue;
              }
      
              yield return result;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2010-11-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-25
        • 1970-01-01
        • 2019-10-25
        • 2013-02-12
        相关资源
        最近更新 更多