1

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
Community
  • 1
  • 1
drzaus
  • 24,171
  • 16
  • 142
  • 201
  • Since this is a little esoteric, the whole point of the question is that, even though I can just "be safe" and use the bitwise shifting, I assume it's slightly less efficient to use an operation than just declare the value (and DWORD syntax is easier than multiplying it out explicitly) – drzaus Jun 21 '12 at 16:23
  • Are you saying that the behavior in VS2012 RC is different from, say VS 2010? If yes, then this may be a VS2012 / C# compiler bug. Report to MSFT quick! – dthorpe Jun 21 '12 at 16:26

1 Answers1

6

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.

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • Agreed; I definitely just forgot/missed the doubling in all the examples I've ever seen/used. And even though "two - four - eight" is easy to remember, I think that's why bitshifting looked so attractive, since you just increment the shift. – drzaus Jun 21 '12 at 16:27
  • Really? I need to wait 8 more minutes to mark this as the answer? – drzaus Jun 21 '12 at 16:28