1

Am I fundamentally misunderstanding how HasFlags works? I cannot understand why this code is failing.

This code takes a value and determines if it is a valid combination of values of my Enum.

Two sub groups of Enum values are identified by ORing other members: JustTheMonths and Exclusives. JustTheMonths is declared in the Enum, and Exclusives is built in the validation method.

When I pass 1 or 2 (Unassigned or Unknown) to this method, it correctly identifies them as valid - Exclusives, but not members of JustTheMonths.

But when I pass 4 to this code, it correctly identifies it as a member of the whole set, but incorrectly identifies it as a member of sub-group JustTheMonths.

What am I doing wrong here? Why does my code think that 4 is a member of (8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8172 | 16344) ?

    private void Form1_Load(object sender, EventArgs e)
    {
        FloweringMonth test = FloweringMonth.NotApplicable;

        if (IsValidFloweringMonthValue((int)test))
        {
            System.Diagnostics.Debug.WriteLine("Valid");
        }
        else
        {
            System.Diagnostics.Debug.WriteLine("Not Valid");
        }
    }

    [Flags]
    public enum FloweringMonth
    {
        Unassigned = 1, Unknown = 2, NotApplicable = 4,
        Jan = 8, Feb = 16, Mar = 32, Apr = 64, May = 128, Jun = 256,
        Jul = 512, Aug = 1024, Sep = 2048, Oct = 4086, Nov = 8172, Dec = 16344,
        JustMonths = (Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec)
    }

    public static bool IsValidFloweringMonthValue(int value)
    {
        FloweringMonth incoming = (FloweringMonth)value;

        FloweringMonth AllVals = FloweringMonth.Unassigned | FloweringMonth.Unknown |
            FloweringMonth.NotApplicable | FloweringMonth.Jan | FloweringMonth.Feb | 
            FloweringMonth.Mar | FloweringMonth.Apr | FloweringMonth.May | 
            FloweringMonth.Jun | FloweringMonth.Jul |  FloweringMonth.Aug | 
            FloweringMonth.Sep | FloweringMonth.Oct | FloweringMonth.Nov | FloweringMonth.Dec;

        // does the incoming value contain any enum values from AllVals?
        bool HasMembersOfAll = AllVals.HasFlag(incoming);
        if (!HasMembersOfAll) return false;

        // does the incoming value contain any enum values from JustTheMonths?
        bool HasMembersOfMonths = FloweringMonth.JustMonths.HasFlag(incoming);

        // does it contain any enum values from the set of three exclusive values?
        FloweringMonth Exclusives = (FloweringMonth.Unassigned | 
            FloweringMonth.Unknown | FloweringMonth.NotApplicable);
        bool HasMembersOfExclusives = Exclusives.HasFlag(incoming);

        // an exclusive value cannot be mixed with any month values
        if (HasMembersOfMonths && HasMembersOfExclusives) return false;  // bad combo

        // an exclusive value cannot be mixed with other exclusive values
        if (incoming.HasFlag(FloweringMonth.Unassigned) && 
            incoming.HasFlag(FloweringMonth.Unknown)) return false;
        if (incoming.HasFlag(FloweringMonth.Unassigned) && 
            incoming.HasFlag(FloweringMonth.NotApplicable)) return false;
        if (incoming.HasFlag(FloweringMonth.Unknown) && 
            incoming.HasFlag(FloweringMonth.NotApplicable)) return false;

        return true;
    }
Cardinal Fang
  • 283
  • 2
  • 12
  • From https://msdn.microsoft.com/en-us/library/system.enum(v=vs.110).aspx: . .Define enumeration constants in powers of two, that is, 1, 2, 4, 8, and so on. This means the individual flags in combined enumeration constants do not overlap. – Cardinal Fang Sep 27 '16 at 01:08
  • @m-y Um, no. The compiler assigns `0, 1, 2, 3, 4,...` in the absence of explicit values. The `Flags` attribute does not alter this behavior. – Kenneth K. Sep 27 '16 at 01:09
  • @KennethK.: I stand corrected, you are correct. – myermian Sep 27 '16 at 11:06

2 Answers2

5

Following @dukedukes answer, the problem was there your multiples of 2 were off.

One way to avoid this mistake is to use bitwise operations on the right-hand side.

[Flags]
enum Months
{
    January = 1 << 3, // 8
    February = 1 << 4, // 16
    March = 1 << 5, // 32
}
Dennis
  • 20,275
  • 4
  • 64
  • 80
2

Your multiples of 2 are incorrect. 4086 should be 4096, 8172 should be 8192, etc...

dukedukes
  • 266
  • 5
  • 10
  • 3
    to avoid this kind of mistake, use power of two. For ex: `Unassigned = 2^0, Unknown=2^1, NotApplicable=2^2, Jan=2^3, Feb = 2^4,` ... – kurakura88 Sep 27 '16 at 01:00
  • God. How could I miss that. Thanks! Stop doing my own math late at night... @kurakura88, brilliant idea. – Cardinal Fang Sep 27 '16 at 01:12
  • @kurakura88 C# doesn't have a PowerOf operator; what you suggested ("^")is a bitwise operator. Nice subtle bug you gave me, that took some unwinding. Need to use the Math.Pow static method. You might want to edit your answer, which I had just gleefully copied into my code :-) – Cardinal Fang Sep 27 '16 at 01:52
  • @kurakura88 Sorry, should have been a smiley after the "nice subtle bug" sentence. I in no way mean to dis your help. But it was fun debugging that bitwise operator, which the compiler was happy to have there. :-) – Cardinal Fang Sep 27 '16 at 02:04
  • Sorry to confuse you. Yes, please use the bitwise to do power of two like Dennis mentioned. I was drunk typing that. :P (StackOverflow only allows me to delete my comment, if you'd like). The comment wasn't intended for the answer, because I don't want to steal dukedukes spotlight. – kurakura88 Sep 27 '16 at 02:08