Two posts ago, I talked about the C# enum and some of its pitfalls (here). This post continues with a discussion of the fundamentals of enums by continuing with using enums and bit-flags.
Defining a [Flags] Enum
Now, we’ve seen previously that enums are typically used when you want to represent a type that can be one of a distinct set of values. But they can also be used to store a combination of discrete values.
That is, the standard use of an enumeration is to support mutex options – such as an order can be a New or Modify but not both. But it is not limited to that, you can form a value that is a combination of enumeration values if the values are structured in the right way – for example, a file can be opened for Read access, Write access, or both.
For example, what if we created an enum like this:
[Flags]
public enum MessagingProperties {
// No options selected (value is 0)
None = 0,
// messages are not discarded if subscriber is slow (value is 1)
Durable = 1,
// messages are saved to disk in case messaging crashes (value is 2)
Persistent = 2,
// messages are buffered at send/receive point so not blocking (value is 4)
Buffered = 4,
}
You may notice we decorated our enum with a [Flags] attribute. You may also notice we skipped the value of 3 in our enumeration, we’ll get to each of these points in turn in the sections that follow, for now take it on faith that these steps are best practices and you’ll soon see the reason.
So now, this definition of a [Flags] enum will allow us to have a MessagingProperties value that is both Durable and Persistent, or is both Buffered and Durable, or is all three, or is none of the three, and so on and so on. But how would we represent this in code?
We do this by combining the flags using bitwise logic (those of you who come from C++ should feel right at home here) using the OR (‘|’) operator. Note that we use the bitwise OR (‘|’) and not the logical OR (‘||’). Attempting to use the logical operators to perform bitwise logic results in a compile time error (and if this were C++ it wouldn’t do what you wanted anyway), so always use the bitwise operators when combining and testing enum values.
Combining Bitwise Values
Remember that the binary OR operator will combine the bits of both numbers such that the resulting bit is 1 if either source bit is 1. Otherwise it’s zero:
1000 01001
0010 01011
---------- OR
1010 01011
Notice that in the example above, every place where either operand had a 1 bit, the result bit is also 1. The only place where the result is a 0 bit is where both source bits were 0.
This is also why we use the binary OR instead of addition. If we were to do addition (+) of the two binary values, it is true we would get 1 where only one of the operand bits was 1, but if they were BOTH 1, we’d result in 0 and carry a 1 to the next bit:
1000 01001
0010 01011
---------- ADD
1010 10100
Notice the results are not the same! Thus if you try to use addition (+) instead of the bitwise OR (|), if the bit is already set, you won’t keep it, but you’ll potentially set or unset the next higher bits. This is why we always use bitwise OR for safety.
So armed with this knowledge, we can try to combine elements in our enum:
MessagingProperties result = MessagingProperties.Durable | MessagingProperties.Persistent;
Now, you’ve set the 1s bit and the 2s bit because the default value of Durable is 1 and the default value of Persistent is 3 due to their position in the enum and the fact we haven’t overridden our values. So now, if we combine them using OR like above, we get:
0000 0001 (binary representation of 1)
0000 0010 (binary representation of 2)
--------- OR
0000 0011 (result of 3)
Thus, both bits have been set for each value. It might be tempting to think of this as an AND instead of an OR, but you must keep in mind the binary math. Combining in binary math is an OR operation, not an AND operation. Let’s look, if we had tried to use AND:
// wrong, use | not & to combine
MessagingProperties result = MessagingProperties.Durable & MessagingProperties.Persistent;
Then we would have gotten:
0000 0001 (binary representation of 1)
0000 0010 (binary representation of 2)
--------- AND
0000 0000 (result of 0)
Whoa! Zero? Yes, remember that the bitwise and sets a resulting bit to 1 only if BOTH source bits are 1. So combining our bits with AND isn’t an option.
Default Enumeration Values Are Not Appropriate for Bitwise Enums
Remember I said that the default numbering of enum values was to start with 0 and increase by one for each value? Well, this is death for a bit-flags enum that has more than 3 values! First of all, your first value is always zero by default. What if we had:
[Flags]
public enum ShapeProperties {
Fill,
Outline,
Shadow,
Dither,
}
Well now we have a problem because it’s impossible to set the Fill flag because it’s value is 0, and anything ORd with 0 is itself. Thus, with bitwise enums you should either start at 1, or define a None value with value zero.
Another problem is the default incrementation of the values. Remember that Dither will default to 3. Well, what is 3 in binary?
0000 0011 (binary representation of 3)
Notice that the binary 3 is the combination of the binary 1 and binary 2 (1s and 2s bits both set). This means that if we used this enumeration as is, ShapeOptions.Dither would be equal to ShapeOptions.Outline | ShapeOptions.Shadow which is not what we want at all. We may think we’re setting Dither, but in reality it will interpret this as being Outline and Shadow.
This is why we must make sure our values are bit-unique. That is, the integer value of each enumerated value must be represented by a single, unique bit. The easiest way to do this is by assigning powers of two (you can use decimal or hexadecimal if you prefer). That’s why our MessagingProperties defined at first didn’t accept the default values in our first code snippet. Also, if we wanted, we could have defined the values using hex:
[Flags]
public enum MessagingProperties {
// No options selected (value is 0)
None = 0x00,
// messages are not discarded if subscriber is slow (value is 1)
Durable = 0x01,
// messages are saved to disk in case messaging crashes (value is 2)
Persistent = 0x02,
// messages are buffered at send/receive point so not blocking (value is 4)
Buffered = 0x04
}
As long as we stick with powers of two we are guaranteed each value will be a single, unique bit. This does create a little more work on our end to guarantee that the bits are unique when we add new values.
Now when we look at Buffered in binary, it is no longer the combination of Durable ORd with Persistent:
Durable | Persistent :
0000 0001(Durable = 1)0000 0010(Persistent = 2)-- -- -- -- -OR
0000 0011(result = 3)
Versus Buffered :
0000 0100(Buffered = 4)
In fact, if we combine them all, we can see that all the bits fit together nicely and are unique:
MessagingProperties result = MessagingProperties.Durable |
MessagingProperties.Persistent |
MessagingProperties.Buffered;
Results in:
0000 0001 (Durable = 1)
0000 0010 (Persistent = 2)
0000 0100 (Buffered = 4)
--------- OR
0000 0111 (result = 7)
Getting Values Back Out of Bit-flags Enum
So how do we get values back out of a bit-flags enum? Well, there’s two ways to do this, we can either use the new C# 4 Enum.HasFlag() method, or we can use a binary AND (&) to test the bits.
First, let’s look at the old-school bitwise AND. Let’s say you wanted to write a method to see if a MessagingProperties value had any of the flags set:
public static void TestBits(MessagingProperties value) {
if ((value & MessagingProperties.Buffered) != 0) {
Console.WriteLine("Buffered.");
}
if ((value & MessagingProperties.Persistent) != 0) {
Console.WriteLine("Persistent");
}
if ((value & MessagingProperties.Durable) != 0) {
Console.WriteLine("Durable");
}
}
Notice that when testing using the old bitwise AND method (&) we AND the value we are testing against the flag value we want to check, and compare for a non-zero result. There are other ways to do this, of course, but this is the most typical. This is because remember that in a bitwise AND, the resulting bits are only set if both source bits are binary 1s. Let’s assume that Durable and Persistent are set:
MessagingProperties result = MessagingProperties.Durable | MessagingProperties.Persistent;
If we compare for Durable, we get:
0000 0011 (result = Durable | Persistent)
0000 0001 (Durable)
--------- AND
0000 0001 1 bit set which is != 0 therefore Durable is set.
Likewise if we compare for Buffered, we should get zero:
0000 0011 (result = Durable | Persistent)
0000 0100 (Buffered)
--------- AND
0000 0000 No bit set which is == 0 therefore Buffered is not set.
So if we run result through this function, we will get:
Persistent
Durable
Which is what we expect. So that’s using the old C++-ish bitwise logic for detecting bits. It so happens that .Net 4.0 has blessed us with a much easier-to-read method of checking enum flags, the Enum.HasFlag()method. We could re-write our flag checker to the following:
public static void TestBits(MessagingProperties value) {
if (value.HasFlag(MessagingProperties.Buffered)) {
Console.WriteLine("Buffered.");
}
if (value.HasFlag(MessagingProperties.Persistent)) {
Console.WriteLine("Persistent");
}
if (value.HasFlag(MessagingProperties.Durable)) {
Console.WriteLine("Durable");
}
}
Now the code is much easier to read and more meaningful.
The Curious FlagsAttribute
You probably noticed the [Flags] attribute applied to our enum when we started talking about bit-flags. This allows for some methods to work better with bit-flags in our enums. You may be surprised, though, to know that everything we’ve done so far on this blog entry can be done without it! In fact, the only thing the [Flags] attribute does is to allow the Enum.ToString() methodto print multiple flags for a bit-flag value.
Here’s the breakdown:
- Only works with [Flags]:
- Allow ToString() to print comma-separated list of enum flag values.
- Works regardless of whether [Flags] is used or not:
- Enum.Parse() will handle comma-separated list of values and combine them.
- Bit-wise math works (will not even warn you if no [Flags] – though Resharper does).
- What [Flags] does not do:
- enum will not automatically assign powers-of-two, always increments by 1 by default.
So the only functionality that adding [Flags] does is give you a better ToString(). Don’t get me wrong, I’m not saying ignore the [Flags] attribute, but I am saying that you have to be mindful of what it doesn’t do. The [Flags] attribute does give you a better ToString() after all and it gives you a good semantic aid that shows the intention of the enum which makes your code better documented and easier to maintain.
Technorati Tags: C#,Software,Fundamentals
Technorati Tags: .NET, C#, CSharp, C# Fundamentals, enum Big Flags Enumerations