Search code examples
c#enumsbit-manipulationbitflags

How does c# flags attribute enum bit combining work?


The Flags attribute for enums allows variables of that enum type to take on more than one value through bit logic. That is why it is recommended for each enum member to have a numeric value which is a power of 2 or 0.

However C# does not prevent you from defining a member with a value of, say, 3.

Which raises the question - how does C# combine flags which have members with a numeric value that is not 0 or 2 raised to a power?

For example, an ordinary flags enum would produce something like (taken from the MSDN link above):

// Define an Enum with FlagsAttribute.
[Flags] 
enum MultiHue : short
{
   None = 0,
   Black = 1,
   Red = 2,
   Green = 4,
   Blue = 8
};

static void Main( )
{
  // Display all combinations of values, and invalid values.
  Console.WriteLine("\nAll possible combinations of values with FlagsAttribute:");

  for( int val = 0; val <= 16; val++ )
     Console.WriteLine( "{0,3} - {1:G}", val, (MultiHue)val);
}

//       All possible combinations of values with FlagsAttribute:
//         0 - None
//         1 - Black
//         2 - Red
//         3 - Black, Red
//         4 - Green
//         5 - Black, Green
//         6 - Red, Green
//         7 - Black, Red, Green
//         8 - Blue
//         9 - Black, Blue
//        10 - Red, Blue
//        11 - Black, Red, Blue
//        12 - Green, Blue
//        13 - Black, Green, Blue
//        14 - Red, Green, Blue
//        15 - Black, Red, Green, Blue
//        16 - 16

However if I were to add another member to the "MultiHue" enum with a numeric value of "3", this is produced:

// Define an Enum with FlagsAttribute.
[Flags] 
enum MultiHue : short
{
   None = 0,
   Black = 1,
   Red = 2,
   Lime = 3,
   Green = 4,
   Blue = 8
};

static void Main( )
{
  // Display all combinations of values, and invalid values.
  Console.WriteLine("\nAll possible combinations of values with FlagsAttribute:");

  for( int val = 0; val <= 16; val++ )
     Console.WriteLine( "{0,3} - {1:G}", val, (MultiHue)val);
}

  0 - None
  1 - Black
  2 - Red
  3 - Lime
  4 - Green
  5 - Black, Green
  6 - Red, Green
  7 - Lime, Green
  8 - Blue
  9 - Black, Blue
 10 - Red, Blue
 11 - Lime, Blue
 12 - Green, Blue
 13 - Black, Green, Blue
 14 - Red, Green, Blue
 15 - Lime, Green, Blue
 16 - 16

Notice how different combinations can be represented with different members, for example:

4 is "Green", however it can be "Lime, Black" just as well
7 is "Lime, Green", instead of the previous "Black, Red, Green"
etc.

I know this is a bad practice but I want to know how does the compiler decide what combination of members to use for a certain bit combination?


Solution

  • It's not about banning or allowing combinations or bitwise operators (either is allowed with or without the Flags attribute) as indicating that bitwise makes sense, and affecting how ToString() works.

    Consider:

    public enum Color
    {
        Black = 0,
        Blue = 1,
        Green = 2,
        Red = 4
    }
    

    This would allow us to meaningfully talk about 8 colours, depending on whether the given blue, green or red flags were set.

    We could define these combinations within the enum:

    public enum Color
    {
        Black = 0,
        Blue = 1,
        Green = 2,
        Cyan = 3,
        Red = 4,
        Magenta = 5,
        Yellow = 6,
        White = 7
    }
    

    Note that here the functionality is the same: Color.Green | Color.Red has the same value of 6 either way, which of course would indicate yellow. The difference is in whether we give it the convenient label of Yellow or not.

    The main difference between my enum and yours is that my combinations make some sort of general sense, because green and red light mix to make yellow, and so on. Generally we'll only find it useful to name the 1-bit only values (1, 2, 4, 8…) but sometimes naming particular common combinations is useful to the coder who is using the enum. A real example of this is DateTimeStyles. In that enum, AllowLeadingWhite has a value of 1, AllowTrailingWhite has a value of 2, AllowInnerWhite has a value of 4, and AllowWhiteSpaces has a value of 7 as a convenient way to indicate all of those three flags are set.