2

I am trying to have a method which fills a list based on the flags set in the enum. My problem is that the iteration through the enum iterates over all elements and not only the elements with one bit set:

using System;
using System.Collections.Generic;
                    
public class Program
{   
    [Flags]
    public enum Modes
    {
        A   = 1 << 0,
        B   = 1 << 1,
        C   = 1 << 2,
        AC  = A | C,
        All = ~0
    }
    
    public class Type
    {
        public Type(Modes mode){}
    }
    
    public static List<Type> Create(Modes modesToCreate = Modes.All)
    {
        var list = new List<Type>();
        foreach(Modes mode in Enum.GetValues(typeof(Modes)))
        {
            if(modesToCreate.HasFlag(mode))
            {
                Console.WriteLine(mode);    
                list.Add(new Type(mode));
            }
        }
        Console.WriteLine();
        return list;
    }
    
    public static void Main()
    {
        Create();
        Create(Modes.A | Modes.C);
    }
}

Real output:

A
B
C
AC
All

A
C
AC

Desired Output:

A
B
C

A
C

How can I ignore the combined flags on the iteration or only iterate over the single bit enum values?

RoQuOTriX
  • 2,871
  • 14
  • 25
  • Ignore anything which doesn't have a single bit set? There are multiple ways to check whether a number is a power of 2, e.g. `BitOperations.PopCount(x) == 1`, `((x & (x - 1)) == 0`, `Math.Log10(x) % 1 == 0`, etc – canton7 Nov 04 '21 at 14:01
  • @canton7 that feels to c-stylish and not the "c#-way". Maybe I am making things to complicated but I thought there would be some "in-build" solution. – RoQuOTriX Nov 04 '21 at 14:02
  • 2
    C# enums are very C-ish anyway -- you set bits with `|`, test them with `&`, etc – canton7 Nov 04 '21 at 14:04

1 Answers1

3

You could use BitOperations.PopCount() to check if the mode has more than one bit set:

public static List<Type> Create(Modes modesToCreate = Modes.All)
{
    var list = new List<Type>();

    foreach (Modes mode in Enum.GetValues(typeof(Modes)))
    {
        if (modesToCreate.HasFlag(mode) && BitOperations.PopCount((uint)mode) == 1)
        {
            Console.WriteLine(mode);
            list.Add(new Type(mode));
        }
    }

    Console.WriteLine();
    return list;
}

If using .net Framework 4.8 or earlier, you can't use PopCount. In that case, you can count the bits using one of the answers to this question:

How to count the number of set bits in a 32-bit integer?

Alternatively (and probably better) you can just use the well-known bit-twiddle to determine if an integer is a power of two:

(n & (n - 1)) == 0

In which case the code would be:

if (modesToCreate.HasFlag(mode) && (mode & (mode - 1)) == 0)

Although for clarity I'd write that as:

bool isPowerOfTwo = (mode & (mode - 1)) == 0;

if (isPowerOfTwo && modesToCreate.HasFlag(mode))

And as PanagiotisKanavos points out in a comment below, in .net 6.0+ you can use BitOperations.IsPow2():

if (modesToCreate.HasFlag(mode) && BitOperations.IsPow2((uint)mode))
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    `isPowerOfTwo` ?? That [new method added to .NET 6](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.bitoperations.ispow2?view=net-6.0) that people were wondering about? I assumed that was needed to optimize some common case but didn't realize it could be that common – Panagiotis Kanavos Nov 04 '21 at 14:19
  • @PanagiotisKanavos Ah of course, that would be better than using `PopCount()` - I'll update the answer. – Matthew Watson Nov 04 '21 at 14:22
  • Interesting that `IsPow2` doesn't use `Popcnt` if available – canton7 Nov 04 '21 at 14:35
  • 1
    @canton7 Does it use the more optimised `(n & (n - 1)) == 0`? (Although I'd think that a processor intrinsic to compute the hamming weight would be faster) – Matthew Watson Nov 04 '21 at 14:35
  • It does use that, but is that more optimized? Popcnt is 3-cycle latency / 1-cycle throughput. It's tiny fractions of time either way, though – canton7 Nov 04 '21 at 14:37
  • Yeah, not sure then. Only Microsoft know that! – Matthew Watson Nov 04 '21 at 14:40
  • 1
    Benchmarks here: https://github.com/dotnet/runtime/pull/36163 . So, minimal difference on modern processors, but there's a hit with popcnt on older processors – canton7 Nov 04 '21 at 14:41
  • It's good to see that MS have paid that much attention to optimising the performance! – Matthew Watson Nov 04 '21 at 14:54
  • 1
    Searching for `IsPow2` in the dotnet repo includes a reference to [AlignedAlloc](https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeMemory.Windows.cs#L24). Aligned memory allocation is one case where every cycle counts. Other hits check whether a type is aligned before vectorizing it. – Panagiotis Kanavos Nov 04 '21 at 15:47