Mr Moose
Mr Moose

Reputation: 6344

Testing for bitwise Enum values

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

Upvotes: 18

Views: 18667

Answers (7)

Ahmad Kamran
Ahmad Kamran

Reputation: 29

In model as_enum :shifts, { day_shift: 0, evening_shift: 1, night_shift: 2, }, accessor: :bitwise

And in Views Model.enum_collection(:shifts)

Upvotes: 0

Mitch Wheat
Mitch Wheat

Reputation: 300529

  1. Yes.

  2. Yes

  3. Both logical and bitwise could be used. Usage depends on whether all is all bits set or just the bitwise OR of all the values you've defined.

  4. Yes, but not because of None. A switch compares a single value, whereas a bit field can obviously have multiple values.

As others have noted Enum contains HasFlag().

Upvotes: 1

porges
porges

Reputation: 30580

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.

Upvotes: 18

Rick Sladkey
Rick Sladkey

Reputation: 34240

Is my understanding of a logical comparison correct given my example above?

Yes, logical in this context means the equality and inequality operators.

Is the way I am performing a bitwise comparison correct?

Yes, but there is an easier way: Enum.HasFlag. For example:

tct.HasFlag(TrainingComponentTypes.Qualification)

instead of:

(tct & TrainingComponentTypes.Qualification) == TrainingComponentTypes.Qualification

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?

I think it is better to define All in the enum itself as the bitwise OR of all its parts. But you'll see people do it both ways.

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)?

No, not at all. Feel free to use them is switch statements. The case values must be constants but they can be expressions and are tested for equality. The compiler will tell you if you do something silly like try to use the same case value twice.

Upvotes: 9

Yaur
Yaur

Reputation: 7452

1&2 look fine

3. All as you have it defined can't be combined with anything without losing data. If "all" is a real value that you expect to receive from the server you should probably change it to 128.

otherwise it is a convenience value that you can use to test if any value is set... you shouldn't need this unless your flag values are sent as binary data and packed in a byte that may contain other data.

4. Switch statements could be used but will not work well if/when you have values that have more than one flag, if there are small subsets of valid flag combinations they can still be useful.

Upvotes: 0

lockstock
lockstock

Reputation: 2427

"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 "

It seems you misunderstand how the bitwise enum values work. 'All' is always combined with other values, in fact it is the combination of all the values. Looking at the binary values for your enum:

None = 0,
AccreditedCourse = 1,
Qualification = 10,
Unit = 100,
SkillSet = 1000,
UnitContextualisation = 10000,
TrainingPackage = 100000,
AccreditedCourseModule = 1000000,
All = 1111111

does that help your understanding?

Upvotes: 0

Alex Aza
Alex Aza

Reputation: 78457

1 and 2 - yes, however there is a way to make it a little easier to read:

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

3 - I am not sure if you need All value at all. I would remove it.

4 - Yes, switch statement usually doesn't make sense for Flags enumerations.

Upvotes: 0

Related Questions