Search code examples
c#syntaxenumsflags

Declaring bitwise enums (Flags) - recent c# syntax quirk for "DWORD style"?


Background: Bitwise enums are useful for "more readable" comparison and checking: i.e. OpenFile(write | append).

I've seen several ways to declare bitwise enums in C#, but recently one of the common patterns doesn't seem to return unique values any more, and I was wondering if I'm declaring it wrong or something has changed. I'm talking about the "DWORD" (hex?) style (demonstrated below), which when enumerating in VS2012 RC gives values as 1, 2, 3, 4... instead of expected bitwise doubling.

Can anyone else reproduce this? I'm posting the code I used for verification along with the console output; the weird behavior occurs with ComparisonsDword as you can see by the output for "Flag enum, explicit values with DWORD".

No flags, normal enum

/// <summary>
/// How to compare filter values; no underlying type declared, not flag
/// </summary>
public enum ComparisonsNotInt {
    [Description("x")]
    None
    ,
    [Description("!=")]
    NotEqual
    ,
    [Description("=")]
    Equal
    ,
    [Description(">")]
    GreaterThan
    ,
    [Description("<")]
    LessThan
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsNotFlag

No flags, underlying type = int

/// <summary>
/// How to compare filter values, not flag but underlying type declared
/// </summary>
public enum ComparisonsNotFlag : int {
    [Description("x")]
    None
    ,
    [Description("!=")]
    NotEqual
    ,
    [Description("=")]
    Equal
    ,
    [Description(">")]
    GreaterThan
    ,
    [Description("<")]
    LessThan
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsNotFlag

Flag, implicit value

/// <summary>
/// How to compare filter values; values default to whatever .NET decides
/// </summary>
[Flags]
public enum ComparisonsImplicit : int {
    [Description("x")]
    None
    ,
    [Description("!=")]
    NotEqual
    ,
    [Description("=")]
    Equal
    ,
    [Description(">")]
    GreaterThan
    ,
    [Description("<")]
    LessThan
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsImplicit

Flag, explicit value

/// <summary>
/// How to compare filter values; values explicitly defined with doubled numbers
/// </summary>
[Flags]
public enum ComparisonsExplicit : int {
    [Description("x")]
    None = 0
    ,
    [Description("!=")]
    NotEqual = 1
    ,
    [Description("=")]
    Equal = 2
    ,
    [Description(">")]
    GreaterThan = 4
    ,
    [Description("<")]
    LessThan = 8
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsExplicit

Flag, explicit value using DWORD style Note: this is what's not correctly providing unique values, so that combinations like GreaterThanOrEqual fail.

/// <summary>
/// How to compare filter values; values explicitly defined with DWORD style
/// </summary>
[Flags]
public enum ComparisonsDword : int {
    [Description("x")]
    None = 0x0
    ,
    [Description("!=")]
    NotEqual = 0x1
    ,
    [Description("=")]
    Equal = 0x2
    ,
    [Description(">")]
    GreaterThan = 0x3
    ,
    [Description("<")]
    LessThan = 0x4
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsDword

Flag, explicit value using DWORD style Note: also inappropriate values, just checking if underlying type is affecting the issue.

/// <summary>
/// How to compare filter values; values explicitly defined with DWORD style
/// </summary>
[Flags]
public enum ComparisonsDwordNotInt {
    [Description("x")]
    None = 0x0
    ,
    [Description("!=")]
    NotEqual = 0x1
    ,
    [Description("=")]
    Equal = 0x2
    ,
    [Description(">")]
    GreaterThan = 0x3
    ,
    [Description("<")]
    LessThan = 0x4
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsDword

Flag, explicit value using bitshifting style

/// <summary>
/// How to compare filter values; values explicitly set using shorthand of bitwise shifting
/// </summary>
[Flags]
public enum ComparisonsBitshift : int {
    [Description("x")]
    None = 0
    ,
    [Description("!=")]
    NotEqual = 1 << 0
    ,
    [Description("=")]
    Equal = 1 << 1
    ,
    [Description(">")]
    GreaterThan = 1 << 2
    ,
    [Description("<")]
    LessThan = 1 << 3
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsBitshift

Output from enumeration:

    Plain enum ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 3
    Enum = GreaterThan      ,        Descr = >,     Value = 3   // bad: should be GTE
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Plain enum, underlying int ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 3
    Enum = GreaterThan      ,        Descr = >,     Value = 3   // bad: should be GTE
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, implicit values ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3   // bad: should be GT
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, explicit values ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 4
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 6
    Enum = LessThan         ,        Descr = <,     Value = 8
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 10

    Flag enum, explicit values with DWORD ----                  // all of these are weirdly unexpected
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, explicit values with DWORD, not underlying int ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, explicit values with bitshifting ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 4
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 6
    Enum = LessThan         ,        Descr = <,     Value = 8
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 10

References:

  1. What does the [Flags] Enum Attribute mean in C#?
  2. MSDN Enumeration Types
  3. MSDN FlagsAttribute
  4. Bitwise enum cast return value not expected

For the sake of completeness, I'm amending my original question with the correct usage from @kirk-woll's answer

Corrected DWORD syntax

    /// <summary>
    /// How to compare filter values; values explicitly defined with *correct* DWORD style
    /// </summary>
    [Flags]
    public enum ComparisonsDwordCorrectlyDefined {
        [Description("x")]
        None = 0x0
        ,
        [Description("!=")]
        NotEqual = 0x1
        ,
        [Description("=")]
        Equal = 0x2
        ,
        [Description(">")]
        GreaterThan = 0x4
        ,
        [Description("<")]
        LessThan = 0x8
        ,
        /// <summary>
        /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
        /// </summary>
        [Description("<=")]
        LessThanOrEqual = (Equal | LessThan)
        ,
        /// <summary>
        /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
        /// </summary>
        [Description(">=")]
        GreaterThanOrEqual = (Equal | GreaterThan)
    }//--   enum    ComparisonsDwordCorrectlyDefined

Output from enumeration

    Flag enum, explicit values with correct DWORD ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 4
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 6
    Enum = LessThan         ,        Descr = <,     Value = 8
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 10

Solution

  • Your DWORD hex style is wrong. You're incrementing by one rather than doubling:

    NotEqual = 0x1
    Equal = 0x2
    GreaterThan = 0x3
    LessThan = 0x4
    

    Should be:

    NotEqual = 0x1
    Equal = 0x2
    GreaterThan = 0x4
    LessThan = 0x8
    

    Of course, using the hex style isn't obviously useful until you start getting above 8:

    LessThan = 0x8
    GreaterThanOrEqual = 0x10
    LessThanOrEqual = 0x20
    

    Here it's kind of handy because the doubling progression leads to a simplified pattern rather than having to mentally double it in your head.