1

I have following code and Console.WriteLine is returning Bottom even though Bottom is not in both enum expressions.

Question

What is the logic behind returning Bottom in code snippet given below? My understanding of & operator is that it returns the common part, but in this case there is nothing common between the two enum expressions.

void Main()
{
    Console.WriteLine(( Orientations.Left | Orientations.Bottom) & 
                     (Orientations.Right| Orientations.Top));//returns Bottom
}


[Flags]
public enum Orientations {
Left = 0, Right= 1, Top=2, Bottom =3
};
Sunil
  • 20,653
  • 28
  • 112
  • 197
  • 2
    `(0 | 3) & (1 | 2)` == `3`. – tkausl Dec 01 '17 at 17:30
  • 1
    Enums are just syntactic sugar for "integer" values. You can cast between two different enums, and the compiler will not complain--even if one enum has a value that the other doesn't. – Kenneth K. Dec 01 '17 at 17:31
  • Thanks. As per MSDN docs, it says `The bitwise AND operator (&) compares each bit of the first operand to the corresponding bit of the second operand. If both bits are 1, the corresponding result bit is set to 1. Otherwise, the corresponding result bit is set to 0.`, should it not just return a 0 or 1 but not 3? – Sunil Dec 01 '17 at 17:34
  • 2
    The problem here is that your enums are not properly set to powers of 2 so that the values are represented by individual digits in the binary representation. Bascially the values should be 1, 2, 4, and 8 for this to work and a None value set to 0. – juharr Dec 01 '17 at 17:34
  • You can actually group enum values into another enum value, for example, you could have an enum value of 15 that always includes values 1, 2, 4, 8, and an enum with a value of 240 that always includes enums with values 16, 32, 64, 128 – Novaterata Dec 01 '17 at 17:41
  • If you are still struggling with this, you need to convert your enum values to binary. If there is only a single 1 than it represents a single enum value, if there is multiple 1s than it could represent multiple enum values. Bitwise & and | have to do with the union and intersection of those bit flags – Novaterata Dec 01 '17 at 17:44
  • 1
    As @juharr has said you need the values to be set to powers of two. See this documentation for further guidance (Guidelines for FlagsAttribute and Enum section) https://msdn.microsoft.com/en-us/library/system.flagsattribute(v=vs.110).aspx – Kerri Brown Dec 01 '17 at 17:48

3 Answers3

13

You assign values to the enums, and the operators | and & work on the enum values, like they would work on the corresponding values.

You have set the values of the enum values yourself, and you have not set them orthogonal. Since integers are in fact bitstrings (with fixed length), you can see it as a 32-dimensional vector (with every vector element having domain {0,1}). Since you defined for instance Bottom as 3, it means that Bottom is actually equal to Right | Top, since:

Right | Top
    1 |   2  (integer value)
   01 |  10  (bitwise representation)
   11        (taking the bitwise or)
Bottom

So that means that if you write &, this is a bitwise AND, and |, is a bitwise OR on the values of the enum values.

So if we now evaluate it, we get:

(Orientations.Left|Orientations.Bottom) & (Orientations.Right|Orientations.Top)
(0                | 3                 ) & (1                 | 2)
3                                       & 3
3
Orientations.Bottom

If you want to define four orthogonal values, you need to use powers of two:

[Flags]
public enum Orientations {
    Left = 1,    // 0001
    Right = 2,   // 0010
    Top = 4,     // 0100
    Bottom = 8   // 1000
};

Now you can see the enum as four different flags, and and the & will create the intersection, and | the union of the flags. In comment the bitwise representation of each value is written.

As you can see, we can now see Left, Right, Top and Bottom as independent elements, since we can not find a monotonic bitwise construction ( to combine Left, Right and Top to construct Bottom (except negation).

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Actually `Flags` doesn't do a thing to your code, it will only affect the `ToString` output. – DavidG Dec 01 '17 at 17:33
  • 2
    https://stackoverflow.com/questions/5902967/what-does-the-flags-attribute-really-do Flags does do stuff, just maybe not what people think. Still if an enum is intended to be this way then it should have `[Flags]` – Novaterata Dec 01 '17 at 17:36
  • @Williem Van Onsem, thanks. I am still confused, because of my lack of knowledge. I am understanding `&` as intersection and `|` as union. So, I conclude that there is no intersection between the first and second enum expressions. Is that right? – Sunil Dec 01 '17 at 17:38
  • Yeah, I'd still add it, even if it's only used by another dev as an indication on how to use the enum – DavidG Dec 01 '17 at 17:38
  • So, if there is no intersection then why is 3 returned? If the first expression had `Orientations.Bottom` in it then 3 would be common between the first and second expressions. – Sunil Dec 01 '17 at 17:40
  • 1
    @Sunil Because your enum should be something like this instead: `Left = 1, Right= 2, Top=4, Bottom =8` – DavidG Dec 01 '17 at 17:41
  • @DavidG, ok, so we have to write each integer as a sequence of bits and then compare each bit by position and come up with a new bit pattern; this new pattern is then converted to an integer number. Is this how we arrive at 3 as the result? Or is there a short cut to finding the result? – Sunil Dec 01 '17 at 17:47
  • @David, I understand now. If I used values using powers of 2, then the `&` operator would truly return the common enum vlaue)(s) and the ` |` operator would truly mean any one of these enum values. It would make it very intuitive. – Sunil Dec 01 '17 at 18:08
7

In order for flag enums to work as expected, the enum constants need to be powers of 2.

In your example the binary values look like this (I show 4 bits only for sake of simplicity)

Left   = 0                     0000
Right  = 1                     0001
Top    = 2                     0010
Bottom = 3                     0011
Left | Right | Top | Bottom =  0011 which is 3 or Bottom again 

If you choose powers of 2, exactly one bit is set and you get

Left   = 1 = 2^0               0001
Right  = 2 = 2^1               0010
Top    = 4 = 2^2               0100
Bottom = 8 = 2^3               1000
Left | Right | Top | Bottom =  1111 

I.e., with powers of 2, different bits are set and therefore they combine neatly with the bitwise OR operator (|).

Since C# 7.0 you can use binary literals

[Flags]
public enum Orientations {
    Left   = 0b0001,
    Right  = 0b0010,
    Top    = 0b0100,
    Bottom = 0b1000
};

In previous versions of C# you can also use the left shift operator to get powers of 2

[Flags]
public enum Orientations {
    Left   = 1 << 0,
    Right  = 1 << 1,
    Top    = 1 << 2,
    Bottom = 1 << 3
};

It is a good practice to also include the enum constant None = 0 because enum fields are initialized to default(MyEnum) == 0, otherwise resulting in a value having no corresponding enum constant.

You can also create new combined enum values like this

[Flags]
public enum Orientations {
    None   = 0,
    Left   = 1 << 0,
    Right  = 1 << 1,
    Top    = 1 << 2,
    Bottom = 1 << 3,
    Horizontal = Left | Right,
    Vertical = Top | Bottom,
    All = Horizontal | Vertical
};

Note that every enum has an implicit conversion from 0. Therefore you could do this test

if((myOrientations & Orientations.Vertical) != 0) {
    // We have at least a Top or Bottom orientation or both
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • So, if I used powers of 2 as you suggested, then `&` operator would truly give only the common enum in my question and since there is nothing common between the enum expressions it would return a 0? So, the bitwise operators would be more intuitive. – Sunil Dec 01 '17 at 18:03
  • 1
    You would use the & operator to extract a single enum value from a combined value, e.g. for testing: `if(flags & Orientations.Right == Orientations.Right) { ... }`. – Olivier Jacot-Descombes Dec 01 '17 at 18:13
1

It is & and | bitwise operation. In the example:

(( Orientations.Left | Orientations.Bottom) & 
                     (Orientations.Right| Orientations.Top))

Will replace with

((0 | 3) & (1 | 2)) with in bit (show only last 3 bit):
((000 |011) & (001 | 010))
= (011 & 011)
= 011

011 is 3 in int value which is Orientations.Bottom value. Therefore, It is always returning Orientations.Bottom.

Shuvra
  • 199
  • 11
  • Why did you show only 3 bits and not the full bit pattern for each of the numbers? – Sunil Dec 01 '17 at 17:54
  • 1
    @Sunil For values under 8 you only need 3 bits to represent them. Heck in this case they are all under 4 so you could just represent them with 2 binary digits. – juharr Dec 01 '17 at 17:56