1

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.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119

3 Answers3

2

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;
    }
}
Petrusion
  • 940
  • 4
  • 11
  • WOW! I do not really like the idea of using unsafe code so I went for #2. I try it but I'm getting: CS0315 The type 'DemoSignalRDomain.ItemCategory' cannot be used as type parameter 'T' in the generic type or method 'EnumUtil.AllFlagsViaBitwise()'. There is no boxing conversion from 'DemoSignalRDomain.ItemCategory' to 'System.Numerics.IBitwiseOperators'. Honestly, I'm not sure about the problem I have. I try to implements IBitwiseOperators on my enum but I can't find how. Any help? – Eric Ouellet May 02 '23 at 19:28
  • Also, I changed "unmanaged" for "struct". It seems to be OK. How does it sounds to you? – Eric Ouellet May 02 '23 at 19:30
  • About you flags tests. I'm not sure your test is appropriate. It could depends on what do you consider being a flag. If a flag is a single bit that identify an option, then your test is wrong. For example take values: 1,2,48. It will says OK but 48 is 2 bits. If you want to consider that any option has its own bit set then it seems fine. Personnaly, I prefer to not test it because I think it belongs to the autors to determine its needs. – Eric Ouellet May 03 '23 at 13:47
  • About IBitwiseOperators, how can we add an interface to an enum? Do you have a full working sample for .Net 7.0? – Eric Ouellet May 03 '23 at 13:58
  • Enums should have IBitwiseOperators by default, it sure worked for me. Are you sure you're using the latest .Net and C# versions? – Petrusion May 03 '23 at 17:00
  • As for changing unmanaged to struct, it doesn't really matter in the second way of doing it. In the first one it is needed because unmanaged says the value can be interpreted as a span of bytes safely. (Every enum is unmanaged anyway so I don't see the harm of keeping it instead of struct) – Petrusion May 03 '23 at 17:02
  • I 'm using .Net 7.0 which come with C# 11 (the latest). I still have the same error CS0315 (prob with IBitwiseOperators), I don't understand why. Did you test your function with an enum with [Flags] attribute? – Eric Ouellet May 03 '23 at 18:37
  • As per my readings, it should work with IBitwiseOperators with C# 11 but it doesn't and I can't find why??? – Eric Ouellet May 03 '23 at 19:16
  • Yeah it just simply worked for me with and without the Flags attribute – Petrusion May 04 '23 at 16:12
  • Can you show (add code in your sample) the full code sample? I mean, the declaration of the enum? I was about to ask another question on StackOverflow to understand why I can't run your code on my machine but I wonder if I did everything properly? – Eric Ouellet May 04 '23 at 20:27
  • It still don't work on my computer. I ask the question on StackOverflow (see link at end). It seems that is not yet implemented for .NET 7 (C# 11). Is it possible that you have the preview version of .NET 8 (C# 12) ? https://stackoverflow.com/questions/76177278/error-cs0315-on-code-where-it-shouldnt-for-c-sharp-11?noredirect=1#comment134340590_76177278 ==> Question: Error CS0315 on code where it shouldn't for C# 11 – Eric Ouellet May 05 '23 at 13:53
  • See my previous message. Until I can't run your second solution, which seems to be great. I will select my second answer as the Answer to this question. But I'm confident Microsoft will eventually implement required interfaces and I will modify my answer to your solution at that time. Thank you VERY MUCH for your great solution. I would really liked to make it works on my machine. – Eric Ouellet May 05 '23 at 14:54
  • 1
    @EricOuellet Sorry for not being able to answer for so long. I must have messed up, I could've **sworn** it worked on my machine but now I see it doesn't. I must have made a typo and used the unsafe version instead of the generic version when testing it. It just made sense that enums would have those interfaces I constrained `T` to, guess Microsoft is yet to implement them on enums. Sorry for taking up your time trying to get it to work. The unsafe option does work though, and is what I'd use. I'm going to edit my answer. – Petrusion May 08 '23 at 15:25
  • No problem at all. I'm happy to be aware about those interfaces. I hope Microsoft will eventually implements them. I think it would be better for us, or anything that would make it easier to do binary operation on flags enums. I still prefer not having any unsafe code but I'm happy to know another way to do it and perhaps peoples will prefer yours. Thanks! – Eric Ouellet May 08 '23 at 18:34
0

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.

Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
  • Is there any reason in particular you chose to use `Expression` as opposed to just supplying a method or lambda? – Petrusion May 02 '23 at 16:34
  • @Petrusion, Ouch! Good question. Just supplying a Lambda would require to compile the lambda on each call which is very slow. My experience with lambda is a way to easily express our though and lot easier to understand the goal. But it require to be compile (at least I think) on each call and infer a delay. Also there is a problem with the underlaying type of the enum which can be any 8-16-32-64 bits. But I'm not sure I answer your question properly. If I'm not clear enough perhaps the 2 links could help to better understand and if not, let me know, I will try to explain better. – Eric Ouellet May 02 '23 at 17:26
  • I think you might be confusing some things? When you supply a lambda expression (like `(a, b) => a + b`) it doesn't get compiled multiple times. Anyway, the important thing is the code is needlessly complicated because of the usage of `Expression`. I think I get why you did it that way: because you didn't know how to do bitwise-or in a generic fashion. I'm going to post an answer of how to do this without `Expression` using either unsafe code or static abstract interface members. – Petrusion May 02 '23 at 18:20
  • @Petrusion, lambda should take the context and create a state machine. That takes time. Lamda are always slower than direct code for this reason. Sometimes the performance is very affected. Wouldn't be any restriction to code with unsafe in it? I always thought there were some kind of restricition like not being deployable by the Microsoft app store?? – Eric Ouellet May 02 '23 at 20:04
  • only an **async** lambda creates a state machine *(a state machine that, mind you, is still only compiled once at, well, compile time)*, a normal lambda (like `(a, b) => a + b`) is really just syntactic sugar for writing a method and replacing the lambda with the reference to that method. If the lambda doesn't capture any variables it doesn't even cause any allocation when created, if it does capture something it is going to create a simple object each time it is created. Lambdas are not slower because of compilation, they're slower because they necessitate a vtable lookup and can't be inlined – Petrusion May 03 '23 at 16:57
0

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

        // **************************************************************
    }
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119