Testing for bitwise Enum values

Mr Moose picture Mr Moose · May 24, 2011 · Viewed 16k times · Source

I haven't really used bitwise enums before, and I just want to make sure my testing is correct. I am most interested in testing for the values None and All. We receive data from a webservice that utilises this enum to categorise certain pieces of the data. Given that, I am assuming that nether None nor All would ever be combined with any other value.

Given the following bitwise enum definition;

[System.FlagsAttribute()]
public enum TrainingComponentTypes : int
    {
        None = 0,
        AccreditedCourse = 1,
        Qualification = 2,
        Unit = 4,
        SkillSet = 8,
        UnitContextualisation = 16,
        TrainingPackage = 32,
        AccreditedCourseModule = 64,
        All = 127,
    }

I read the following quote on this MSDN site about FlagAttributes;

Use None as the name of the flag enumerated constant whose value is zero. You cannot use the None enumerated constant in a bitwise AND operation to test for a flag because the result is always zero. However, you can perform a logical, not a bitwise, comparison between the numeric value and the None enumerated constant to determine whether any bits in the numeric value are set.

Does a logical comparison in this instance refer to a normal equality test for enums? For example;

TrainingComponentTypes tct = TrainingComponentTypes.None; 
if (tct == TrainingComponentTypes.None) 
{ ... }

For a bitwise comparison, I am performing the following;

 TrainingComponentTypes tct = TrainingComponentTypes.AccreditedCourse | TrainingComponentTypes.Qualification | TrainingComponentTypes.TrainingPackage;
 Assert.IsTrue((tct & TrainingComponentTypes.AccreditedCourse) == TrainingComponentTypes.AccreditedCourse, "Expected AccreditedCourse as part the enum");

 Assert.IsFalse((tct & TrainingComponentTypes.SkillSet) == TrainingComponentTypes.SkillSet, "Found unexpected SkillSet as part the enum");

Lastly, when testing for all, I have tried both a logical, and bitwise comparison, and they both return the same. Should I be using one over the other here? For example;

TrainingComponentTypes tct = TrainingComponentTypes.All;

Assert.IsTrue((tct & TrainingComponentTypes.All) == TrainingComponentTypes.All, "Expected All as part the enum");
Assert.IsTrue((tct) == TrainingComponentTypes.All, "Expected All as part the enum");
// The follow also pass the assertion for a value of All
Assert.IsTrue((tct & TrainingComponentTypes.Qualification) == TrainingComponentTypes.Qualification, "Expected Qualification as part the enum");
Assert.IsTrue((tct & TrainingComponentTypes.TrainingPackage) == TrainingComponentTypes.TrainingPackage, "Expected TrainingPackage as part the enum");

So in summary, I'd like to know the following about Bitwise enums;

  1. Is my understanding of a logical comparison correct given my example above?
  2. Is the way I am performing a bitwise comparison correct?
  3. What is the right way to handle the "All" value (bitwise or logical). I am not sure if we'd ever receive a value where All was combined with other TrainingComponentTypes. I can't see why we would, but then, you never know?
  4. Am I right in assuming that switch statements basically shouldn't be used for bitwise enums (given none is appears to be a special case and requires a logical comparison)?

Thanks, Chris

Answer

porges picture porges · May 24, 2011

Short answer: Yes :)

Longer:

1) All operations are performed on the integer value of the flags variable, so you can think about them in terms of this.

2) Yes.

3) Either works. However, it's worth noting that if someone shoves an invalid value into a variable then the == TrainingComponentTypes.All version will fail. For example:

var badValue = (TrainingComponentTypes)128 | TrainingComponentTypes.All;
// now badValue != TrainingComponentTypes.All
// but (badValue & TrainingComponentTypes.All) == TrainingComponentTypes.All

For this part:

I am not sure if we'd ever receive a value where All was combined with other TrainingComponentTypes.

I'm not sure you fully understand how the enum works under the covers.

The value of All is:
    127 = 1111111 (binary)

The other values are:
    AccreditedCourse       = 0000001
    Qualification          = 0000010
    Unit                   = 0000100
    SkillSet               = 0001000
    UnitContextualisation  = 0010000
    TrainingPackage        = 0100000
    AccreditedCourseModule = 1000000

As you can see, All is simply the bitwise | of all these values together. You can't combine any other TraningComponentTypes with All, because All already includes them! Also, if you combine them all together with | yourself it's exactly the same as using All directly (so, All is simply a convenience when you define it inside an enum).

4) You could use it to check for None or All but not for other values.

It's worth noting that there is a convenience method on Enum that will do these checks for you: Enum.HasFlag.