4

I have an enum defined like that:

[Flags]
public enum Orientation
{
    North = 1,
    North_East = 2,
    East = 4,
    South_East = 8,
    South = 16,
    South_West = 32,
    West = 64,
    North_West = 128
}

Is there a generic way to tell if exactly one flag is set, multiple or none? I don't care for what the value of the enum is, I just want to know how many flags are set.

This is not a duplicate for counting the number of bits. If I initialize the enum like this and count the number of bits I get 10. But the number of set flags would be 8.

GeographicOrientation go = (GeographicOrientation) 1023;
Norman
  • 3,279
  • 3
  • 25
  • 42
  • 2
    That looks wrong. Can you really have an orientation that is both North and South at the same time? – Jonathan Allen Jun 13 '16 at 17:21
  • @JonathanAllen Yes, don't think of a direction which I can take a time. Think of which directions are available. May be "orientation" is the wrong wording here ;) – Norman Jun 13 '16 at 17:22
  • 1
    @Dennis_E Not really since you can have something like `(Orientation)1023` which has 10 bits set when there are only 8 flags. – juharr Jun 13 '16 at 17:23
  • Ok, just making sure. – Jonathan Allen Jun 13 '16 at 17:26
  • 1
    Your two sentences are subtly contradictory. In one sentence you say that all you care about is zero, one or many, and in the second sentence you say you care about the count. If I give you a jar with some number of pennies in it, it is a *lot* easier to know whether there are zero, one or many pennies than it is to know the exact count of pennies. In the first case you can stop counting when you get to two; in the second, you have to count all the pennies. So which is it? – Eric Lippert Jun 13 '16 at 17:28
  • 1
    This is not a good duplicate, because OP is not asking for how many bits are set. He wants to know if it's (A) zero, (B) one, or (C) more than one. Voting to re-open. – Sergey Kalinichenko Jun 13 '16 at 17:31
  • `(GeographicOrientation) 1023;` is nonsensical unless the REAL question is "how to count the number of theoretical flags in an arbitrary integer." If that is the case, then this sounds like either A) a completely academic question, or B) an X-Y problem. – DVK Jun 13 '16 at 18:06

6 Answers6

7

You can determine this with a simple bit trick after converting the value to int:

int v = (int)enumValue;

  1. If v == 0, then no flags are set
  2. Otherwise, if ((v-1)&v) == 0, then exactly one flag is set
  3. Otherwise, multiple flags are set.

The only tricky one is #2. Here is an explanation: consider a binary number with exactly one bit set to 1. Then subtracting 1 would make the following change:

  0000010000000
-             1
  -------------
  0000001111111

All zeros following the lone 1 become 1s, 1 becomes zero, and the rest of the bits remain the same. AND-ing v and v-1 produces zero, because there is no pair of 1s in the same position between the two numbers.

When there are two or more 1s, the bits to the left of the lone 1 will remain unchanged. Therefore, at least one position will have a 1 in the result of bitwise AND.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
4

you can use this code :

var item = Orientation.North | Orientation.South;
int i = 0;
foreach (Orientation e in Enum.GetValues(typeof(Orientation)))
    if(item.HasFlag(e))
        i++;

Console.WriteLine(i);
Kahbazi
  • 14,331
  • 3
  • 45
  • 76
  • 2
    Could even be one line with Linq `int i = Enum.GetValues(typeof(Orientation)).Cast().Where(o => item.HasFlag(o)).Count();` – juharr Jun 13 '16 at 17:26
  • Thought there was a shorter way than walking a loop or using linq, but obviously there is not... – Norman Jun 13 '16 at 17:44
  • I choose this one as the solution but I actually use juharr's approach (which, technically, is identitical). It seems to be the most reasonable one - even if I like the way Slai was thinking with ToString. – Norman Jun 14 '16 at 06:20
3
var test = Orientation.North;
var flagCount = GetFlagCount(test);

public int GetFlagCount(Enum testValue)
{
    return Enum.GetValues(testValue.GetType()).Cast<Enum>().Count(testValue.HasFlag);
}
Kaspars Ozols
  • 6,967
  • 1
  • 20
  • 33
  • Which target framework are you using? In .Net 4.5.2 an array (GetValues) does not contain a definition for count taking those arguments... – Norman Jun 13 '16 at 17:42
  • Sorry, I was answering from my phone, so didn't have possibility to try it out. I have updated my answer. – Kaspars Ozols Jun 13 '16 at 20:14
2

If you are looking for the "shortest" way:

Orientation o = Orientation.East | Orientation.West;   // o.ToString() = "East, West"
var c = o.ToString().Split().Count(); 

or even shorter:

var c = (o + "").Split().Count(); 

Update

To support values above 255, you can use any of those ugly hacks:

Orientation o = (Orientation) 1023;   
var c = ((Orientation)(byte)o + "").Split().Count();
c = ((Orientation)((int)o & 255) + "").Split().Count();

or just define the enum as byte:

    [Flags]
    public enum Orientation : byte
    {
        North = 1,
        North_East = 2,
        East = 4,
        South_East = 8,
        South = 16,
        South_West = 32,
        West = 64,
        North_West = 128
    }

Update 2 I personally wouldn't use the string method in production code especially when just a bit count is needed.

Anyway, I just thought of another hack just for fun. Base 2 log will return a whole number when one bit is set, -Infinity when 0, and anything else when more than one bit is set. For Example

 Math.Log(0, 2 ) // -Infinity
 Math.Log(0, 64) // 6.0
 Math.Log(0, 65) // 6.0223678130284544

So, (byte)go != 0 can be used to check if any flags are set, and then Math.Log((byte)go, 2) % 1 == 0 to check if only one flag is set.

But, dasblinkenlight's solution seems like the best.

Slai
  • 22,144
  • 5
  • 45
  • 53
  • 1
    This is kind of a cool abuse of ToString(). You can also split on the comma. `foo.ToString() = "Foo, Bizz"` – DVK Jun 13 '16 at 18:02
  • I like the way you're thinking and the tostring-split approach is very neat. But... does ToString behave consistent on any platform, any region, any c# version? – Norman Jun 14 '16 at 06:23
1

Off the top of my head:

var map = 1;
var count = 0;

while (map <= North_West)
{
    if( ((int)Value & map) > 0) 
       count += 1; 
    map = map << 1; //left shift, a.k.a. multiply by 2
}
Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
0

This is not a duplicate for counting the number of bits. If I initialize the enum like this and count the number of bits I get 10. But the number of set flags would be 8.

Please consider this statement directly from MSDN:

Flags enumerations are used for masking bit fields and doing bitwise comparisons.

If you are designing or using the enum in some manner that does NOT allow counting the number of flags using bitwise operations, then you are designing or using the enum improperly.

    [Flags]
    public enum MyEnum
    {
        Foo = 1,
        Bar = 2,
        Bizz = 4,
        Buzz = 8,
        Boo = 16
    }


var foo = MyEnum.Foo | MyEnum.Foo | MyEnum.Bizz;
// foo == MyEnum.Foo | MyEnum.Bizz because setting the same flag twice does nothing

var bar = (MyEnum)27 // Some invalid combination
// bar == 27.  Why would you do this instead of MyEnum.Boo | MyEnum.Buzz | MyEnum.Bar | MyEnum.Foo

If you design your enum properly, you can count the flag, and optionally short circuit if there is more than one flag set since continuing to count would be pointless.

        var foo = MyEnum.Foo | MyEnum.Bizz;
        int fooValue = (int)foo;
        int numberOfFlags = 0;

        while (fooValue > 0 && numberOfFlags < 2) // Stop counting if more than one flag since we don't need exact count
        {
            if ((fooValue & 1) == 1)
            {
                numberOfFlags++;
            }

            fooValue >>= 1;
        }

        Console.WriteLine(numberOfFlags);
DVK
  • 2,726
  • 1
  • 17
  • 20
  • var bar = (MyEnum)27 is nothing I would do in code. But since the value of the enum is stored in a db as an int you could change it to any value there without knowing about the enum. – Norman Jun 14 '16 at 06:17