How to generically combine all bits (I mean OR all values) of a [FLAGS] enum, having only valid values (bits) declared in the enum?

Ex: [Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 } => Expected solution would: return Spades | Clubs | Diamonds | Hearts; but using a generic function.

I try to ask the question previously but I asked the wrong question: Enum fun... Why these 2 errors.... My question was closed but peoples gave me some great clues on how to do it. I finally did it with pretty much works (trial and errors). So I wanted to leave a trace of my solution to help others, and potentially have better solution from other users too.

It is not the first time I need that. Although anybody can do it quickly by OR (val1 | val2 | ...) each values manually, that could lead to runtime error when a new enum value is added and the programmer forget to verify each and every places where the enum is used.

3

There are 3 best solutions below

0
Eric Ouellet On BEST ANSWER

This is my second answer which I prefer because of its advantages:

  • It has no unsafe code.
  • It does not require compilation.
  • It's probably faster on first call than any code requiring compilation
  • It should also be the fastest on subsequent calls, due to the caching

Usage sample:

private ItemCategory _categoryFilter = EnumFlagsAll<ItemCategory>.Value;

Code:

public static class EnumFlagsAll<T> where T : struct, Enum
    {
        // **************************************************************
        static EnumFlagsAll()
        {
            T res = default;
            foreach (T val in Enum.GetValues<T>())
            {
                res = (T)GenericAddBits(res, val);
            }
            Value = res;
        }

        public static T Value { get; }

        // **************************************************************
        /// <summary>
        /// Generic method to add bit(s) from the first value. Will perform an OR.
        /// </summary>
        /// <param name="val"></param>
        /// <param name="bitsToAdd"></param>
        /// <returns></returns>
        public static Enum GenericAddBits(Enum val, Enum bitsToAdd)
        {
            // consider adding argument validation here

            return (Enum)Enum.ToObject(val.GetType(), Convert.ToUInt64(val) | Convert.ToUInt64(bitsToAdd));
        }

        // **************************************************************
    }
5
Eric Ouellet On

Usage sample:

private ItemCategory _categoryFilter = EnumUtil.AllFlags<ItemCategory>();

Code:

public class EnumUtil
    {
        /// <summary>
        /// Usage example: Foreach(MyEnumType enumVal in AllFlags<MyEnumType>())
        /// </summary>
        /// <typeparam name="T">enum type</typeparam>
        /// <returns>Combined enum values</returns>
        public static T AllFlags<T>() where T : struct, Enum
        {
            return Enum.GetValues<T>().Aggregate(EnumAggregator<T>.Aggregator);
        }
    }

public class EnumAggregator<T> where T : struct, Enum
    {
        static EnumAggregator()
        {
            Type underlyingType = Enum.GetUnderlyingType(typeof(T));
            var currentParameter = Expression.Parameter(typeof(T), "current");
            var nextParameter = Expression.Parameter(typeof(T), "next");

            Aggregator = Expression.Lambda<Func<T, T, T>>(
                Expression.Convert(
                    Expression.Or(
                        Expression.Convert(currentParameter, underlyingType),
                    Expression.Convert(nextParameter, underlyingType)
                    ),
                typeof(T)
                ), currentParameter, nextParameter
            ).Compile();
        }

        public static readonly Func<T, T, T> Aggregator;
    }

The main StackOverflow answer which was most useful to me was: C# Method to combine a generic list of enum values to a single value where most parts where there but required to be assemble together. Thanks to @madreflection for its good advises on my initial question and its great answer on the previous SO question link.

I think the code is also pretty much efficient because the lambda is compiled only once.

14
Petrusion On

Here's two ways you can do it without Expression compiling:

First is using unsafe code:

public class EnumUtil
{
    private static unsafe ulong EnumBitsAsUlong<T>(T value) where T : unmanaged, Enum 
    {
        ReadOnlySpan<byte> valueBytes = new(&value, sizeof(T));
                
        ulong valueUlong = 0;
        valueBytes.CopyTo(new Span<byte>(&valueUlong, sizeof(ulong)));

        return valueUlong;
    }
    
    public static T AllFlagsViaUnsafe<T>() where T : unmanaged, Enum
    {
        // any enum fits in an ulong
        ulong result = 0;

        var values = Enum.GetValues<T>();
        foreach (var value in values)
        {
            result |= EnumBitsAsUlong(value);
        }

        return Unsafe.As<ulong, T>(ref result);
    }
}

EDIT: THIS ONE BELOW DOESN'T WORK! It doesn't work because enums don't implement IBitwiseOperators even though they should IMO. I'm keeping it here because in the future the needed interfaces might be implemented on enums.

Second is by constraining T to be able to use the bitwise or operator:

public class EnumUtil
{
    public static T AllFlagsViaBitwise<T>() where T : unmanaged, Enum, IBitwiseOperators<T, T, T>
    {
        return Enum.GetValues<T>().Aggregate((a, b) => a | b);
    }
}

Obviously the second way is better if your C# version supports it

EDIT: Adding versions of code below which throws exceptions when the Enum is not in fact a flags Enum

EDIT: THE ONE WITH IBitwiseOperators DOESN'T WORK! It doesn't work because enums don't implement IBitwiseOperators even though they should IMO. I'm keeping it here because in the future the needed interfaces might be implemented on enums.

public class EnumUtil
{
    private static unsafe ulong EnumBitsAsUlong<T>(T value) where T : unmanaged, Enum 
    {
        ReadOnlySpan<byte> valueBytes = new(&value, sizeof(T));
                
        ulong valueUlong = 0;
        valueBytes.CopyTo(new Span<byte>(&valueUlong, sizeof(ulong)));

        return valueUlong;
    }
    
    public static T AllFlagsViaUnsafe<T>() where T : unmanaged, Enum
    {
        // any enum fits in an ulong
        ulong result = 0;

        foreach (var value in Enum.GetValues<T>())
        {
            var valueUlong = EnumBitsAsUlong(value);

            if ((result & valueUlong) != 0)
            {
                throw new ArgumentException(
                    $"{typeof(T).Name} is not a flags enum. Detected at enum value {value}", nameof(T));
            }
            result |= EnumBitsAsUlong(value);
        }

        return Unsafe.As<ulong, T>(ref result);
    }
    public static T AllFlagsViaBitwise<T>() where T : unmanaged, Enum, IBitwiseOperators<T, T, T>, IEqualityOperators<T, T, bool>
    {
        T result = default;
        foreach (var value in Enum.GetValues<T>())
        {
            if ((result & value) != default)
            {
                throw new ArgumentException(
                    $"{typeof(T).Name} is not a flags enum. Detected at enum value {value}", nameof(T));
            }
            result |= value;
        }
        return result;
    }
}