如何获得枚举的所有可能组合(标志)

时间:2011-05-24 21:17:42

标签: c# enum-flags

[Flags]
public enum MyEnum
{
    None = 0,
    Setting1 = (1 << 1),
    Setting2 = (1 << 2),
    Setting3 = (1 << 3),
    Setting4 = (1 << 4),
}

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

8 个答案:

答案 0 :(得分:4)

未经测试,使用风险自负,但一般应解决问题。 System.Enum不是有效的限制,因为从技术角度来说,C#只允许继承/ 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;
}

这可以通过获取所有值并将它们组合在一起而不是求和,以防包含复合数字。然后它将每个整数调到最大值并用每个标志的反向屏蔽它们,这会导致有效位变为0,从而允许我们识别那些不可能的位。

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

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

答案 1 :(得分:2)

这是一个特定于您的代码示例的解决方案,使用简单的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 :(得分:2)

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

答案 3 :(得分:1)

首先,获取所有单个值的列表。由于你有5个值,即(1 << 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);
}

答案 4 :(得分:1)

因为它是一个标记的枚举,为什么不简单:

  1. 获得枚举的最高价。
  2. 计算组合的数量,即上限。
  3. 迭代每个组合,即从0到上限循环。
  4. 示例如下所示

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

答案 5 :(得分:0)

当我向枚举添加新成员时,我通常不想更新表示枚举最大值的每个变量。 例如,我不喜欢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),就像通常使用的是flag-enumeration一样。

答案 6 :(得分:0)

我可能要晚一点去参加聚会,我的解决方案还包括值以及组合形式的可能文本(“ V1 | V2”,“ V1 | V2 | V3”,等)。

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

注意:仅在将Enum设置为以2为基础的组合时起作用。

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

答案 7 :(得分:0)

此版本的结尾:

未经检查:

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