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:
- What does the [Flags] Enum Attribute mean in C#?
- MSDN Enumeration Types
- MSDN FlagsAttribute
- 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