Click here to Skip to main content
15,887,434 members
Articles / Programming Languages / C# 7.0
Technical Blog

Enums, Flags and Binary Literals

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
13 Nov 2018CPOL8 min read 2.3K  
enum value types, how they can be composed as flags and how C# 7 makes it easier for us to use and understand them

Introduction

In this post, we’re going to look at the enum value types, how they can be composed as flags and how C#7 makes it easier for us to use and understand.

We touched upon enums during the post "The hidden side-effect of enums and values." Now we’re going to have a more in-depth look at them, see what they are and how they work, and we’re going to expand on those ideas.

What are Enums?

Enums are short for enumerations, they represent a set of constants mapped out to a numerical value. This allows us to make use of those values without using “magic” numbers or parsing strings.

Let’s look at an example of an enum and dissect it:

C#
public enum ModeOfTransport : byte
{
    ByCar = 5,
    ByBicycle,
    ByAirplane,
    ByDingy = 10,
    ByCanoe = 10
}

Let’s assume that this enum will be used in a map application to tell us the route to take and how long until we reach the destination.

In this example, we can see the following:

  • An enum represents a number, as such, when we derive it from a numerical value like byte, we are saying that this enum cannot have negative values, and cannot take up more than 256 values (if we try to assign negative numbers or numbers higher than 255, the compiler will issue an error). Do note that if no numerical type is present, then it will default to a 32-bit integer, which for small enums can be a big waste if cumulated.
  • We can assign numerical values to each enum so we can have better control over which number represents which value.
  • Enums, by default, start counting from 0 even if the underlying type is a signed integer, and they keep counting up from where they were last off so ByBicyle will have a value of 6, and if we were to add another enum definition after ByCanoe, it will have a value of 11.
  • In this case, we opted to start from 5 with the first value, but we can also assign the same numerical value to different enum definitions like in the case of a dingy and a canoe. The reasons for using the same numerical value are not encountered often but they do occur, like, for example, someone during the development of the application created a client application that uses both dingy and canoes as means of transportation, but for our algorithm since they are both row-boat, we want to treat them the same.
  • Enums are easier to use with switch cases because during compile time, they are turned into their numerical value, word of caution if we have the same numerical values in the same enum, and we put them in a switch case, the compiler won’t allow us to do that since the value is already registered as a case. Here’s an example:
C#
switch (enumVal)
{
    case ModeOfTransport.ByCar:
        break;
    case ModeOfTransport.ByBicycle:
        break;
    case ModeOfTransport.ByAirplane:
        break;
    case ModeOfTransport.ByDingy:
        break;
    case ModeOfTransport.ByCanoe:
        break;
    default:
        throw new ArgumentOutOfRangeException();
}

This will emit an error saying the following:

The switch statement contains multiple cases with the label value ’10’.

Duplicate case label value ‘ModeOfTransport.ByDingy

What Are Flags and What Do Binary Literals Have to Do With It?

Since enums are a representation of a number, before we can look at flags properly, we have to understand how they are represented in memory and how they can become “composable”.

Take for example, our ModeOfTransport enum, since it “inherits” from a byte that means that the value ranges from 0 to 255 inclusive. So the memory values it can have are from 00 to FF in hexadecimal or from 0000 0000 to 1111 1111 in binary, we will see why the binary form is so important.

If we look at our values now in the binary form, we will see the following:

ByCar = 0000 0101,
ByBicycle = 0000 0110,
ByAirplane = 0000 0111,
ByDingy = 0000 1010,
ByCannoe = 0000 1010

For me, this makes more sense when I look at them like this in binary because it means that every bit in that enum represents something for us. This makes for compact reusable dictionaries, and since enums are value types, it means they always have to have a value and are always copied, from method calls to large arrays.

Now let’s look at the Flags attribute when declaring an enum. For this example, consider we want to make a game in which we want to tell the player in which directions he or she can move:

C#
[Flags]
public enum Directions : byte
{
    North,
    East,
    South,
    West
}

Let’s look at what is wrong in this enum and the several ways we can fix it.

If the player was in a room that had all 4 paths open, then using the current enum, only the West path will show up. Flags are a special type of enum which can be composed of multiple values, hence by convention they are named in a plural form. To understand flags, we must look into binary operations and how that allows us to make one number represent several things at the same time.

In our case, the values are as follows:

Value Decimal Hexadecimal Binary
North 0 0x00 0000 0000
East 1 0x01 0000 0001
South 2 0x02 0000 0010
West 3 0x03 0000 0011

When we compose enums (and any number in general), we use the bitwise operators. Those are & (AND), | (OR), ^ (XOR), (SHIFT RIGHT). Let’s have a quick recap as to how they work and then the problem with the enum above will become obvious.

Operation Operands Result Explanation
& (AND) 0000 0001 0000 0001 When we use binary AND, we compare each bit and if both are 1, then the resulting value is 1, else the value is 0
0000 0011
| (OR) 0000 0001 0000 0011 When we use binary OR, we compare each bit and if either bit is 1, then the resulting value is 1, else the value is 0
0000 0010
^ (XOR or exclusive OR) 0000 0001 0000 0010 When we use binary exclusive OR, we compare each bit and only if one bit is 1 and the other is 0, then the resulting value is 1, else the value is 0
0000 0011
~ (NOT) 0000 0001 1111 1110 When we use binary NOT, we flip the value of each bit, 1 becomes 0 and 0 becomes 1
Unary operation, no second operand
<< (SHIFT LEFT) 1100 0001 1000 0010 When we use binary SHIFT LEFT, all the bits get shifted to the left times the number of the second operand, if we shift past the end, then that bit is lost
1
>> (SHIFT RIGHT) 0000 0011 0000 0001 When we use binary SHIFT RIGHT, all the bits get shifted to the right times the number of the second operand, if we shift past the end, then that bit is lost
1

You can find more information about binary operator here.

Now let’s look at our problem, if we wanted to say the player can go North, West or South, then we would write:

Directions.North | Directions.South | Directions.West

Which would get translated into binary as such:

0000 0000 |
0000 0010 |
0000 0011 =
0000 0011

Because we’re comparing each bit by column, we will end up with the value for West. This is not what we wanted, from 3 choices, we’re down to 1.

To solve this, each enum value must represent a single independent bit, as such the values for our directions should be as follows:

Value Decimal Hexadecimal Binary
North 1 0x01 0000 0001
East 2 0x02 0000 0010
South 4 0x04 0000 0100
West 8 0x08 0000 1000

Notice the common theme, we move one bit to the left and the decimal value gets multiplied by 2. Using these values, we can see that the earlier operation turns into:

C#
0000 0001 |
0000 0100 |
0000 1000 =
0000 1011

Now we have no overlap and our algorithms will work properly because instead of each bit meaning something like in the case of simple enums, we only look at bits at certain positions, that means that for a byte enum, we might have 8 possible base values but 255 combinations of them.

And now, let’s see how we can do this in code in each of the 4 forms, first up decimal:

C#
[Flags]
public enum Directions : byte
{
    North = 1,
    East = 2,
    South = 4,
    West = 8
}

While we might be more familiar with the decimal system, this method of writing it means that we must always hard code the value with a calculation we must do, can anyone tell me what is the value of 2 raised to the power of 17?

Now for the hexadecimal form (or hexadecimal literals):

C#
[Flags]
public enum Directions : byte
{
    North = 0x1,
    East = 0x2,
    South = 0x4,
    West = 0x8
}

Not a lot of change in this form because we have such a small number of values, but the advantage of this approach is that you can write very large numbers in a small screen space and they are easy to turn back into base 2 (though not off the top of the head).

C#
[Flags]
public enum Directions : byte
{
    North = 1 << 0,
    East = 1 << 1,
    South = 1 << 2,
    West = 1 << 3
}

This one is my second favorite approach, the down side is that you need to understand how bit shifting works, the advantage is that you can add any value without doing any computation, for example, here I know that West is 1 shifted 3 times, which means it’s the 4th bit.

And now the latest mode of writing comes from the release of C# 7 under the name of binary literals.

C#
[Flags]
public enum Directions : byte
{
    North   =   0b0000_0001,
    East    =   0b0000_0010,
    South   =   0b0000_0100,
    West    =   0b0000_1000
}

This has become my new favorite way of writing enum flags, the downside is obvious, it is very verbose, but the advantages for me are obvious, I can see each bit in each position and I don’t need to calculate them. Do note that the front zeros are not mandatory but it’s a good habit to write them like this because it keeps everything in line and also this way, we can visualize the maximum and minimum value for my underlying numeric type.

Conclusion

I hope you enjoyed our little trip through the world of bits and I hope this will be of help to you in the future, as it has been for me.

Thank you and see you next time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Romania Romania
When asked, I always see myself as a .Net Developer because of my affinity for the Microsoft platform, though I do pride myself by constantly learning new languages, paradigms, methodologies, and topics. I try to learn as much as I can from a wide breadth of topics from automation to mobile platforms, from gaming technologies to application security.

If there is one thing I wish to impart, that that is this "Always respect your craft, your tests and your QA"

Comments and Discussions

 
-- There are no messages in this forum --