Why does [Flag]'d enums start at 0 and increment by 1?
Edit: It seems most people misunderstood my question.
I know how enum works, and I know binary. I'm wondering why the enums with the [Flags] attribute is designed the way it is.
Original post:
This might be a duplicate, but I didn't find any other posts, so here goes.
I bet there has been some good rationale behind it, I just find it a bit bug prone.
[Flag]
public enum Flagged
{
One, // 0
Two, // 1
Three, // 2
Four, // 3
}
Flagged f; // Defaults to Flagged.One = 0
f = Flagged.Four;
(f & Flagged.One) != 0; // Sure.. One defaults to 0
(f & Flagged.Two) != 0; // 3 & 1 == 1
(f & Flagged.Three) != 0; // 3 & 2 == 2
Wouldn't it have made more sense if it did something like this?
[Flag]
public enum Flagged
{
One = 1 << 0, // 1
Two = 1 << 1, // 2
Three = 1 << 2, // 4
Four = 1 << 3, // 8
}
Flagged f; // Defaults to 0
f = Flagged.Four;
(f & Flagged.One) != 0; // 8 & 1 == 0
(f & Flagged.Two) != 0; // 8 & 2 == 0
(f & Flagged.Three) != 0; // 8 & 4 == 0
(f & Flagged.Four) != 0; // 8 & 8 == 8
Of course.. I'm not quite sure how it should handle custom flags like this
[Flag]
public enum Flagged
{
One, // 1
Two, // 2
LessThanThree = One | Two,
Three, // 4? start from Two?
LessThanFour = Three |开发者_高级运维 LessThanThree,
Three, // 8? start from Three?
}
The spec gives some guidelines
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.
But this should perhaps be done automatically as I bet you would never want my first example to occur. Please enlighten me :)
The Flags
attribute is only used for formatting the values as multiple values. The bit operations work on the underlying type with or without the attribute.
The first item of an enumeration is zero unless explicitly given some other value. It is often best practice to have a zero value for flags enumerations as it provides a semantic meaning to the zero value such as "No flags" or "Turned off". This can be helpful in maintaining code as it can imply intent in your code (although comments also achieve this).
Other than that, it really is up to you and your design as to whether you require a zero value or not.
As flag enumerations are still just enumerations (the FlagsAttribute
merely instructs the debugger to interpret the values as combinations of other values), the next value in an enumeration is always one more than the previous value. Therefore, you should be explicit in specifying the bit values as you may want to express combinations as bitwise-ORs of other values.
That said, it is not unreasonable to imagine a syntax for flags enumerations that demands all bitwise combinations are placed at the end of the enumeration definition or are marked in some way, so that the compiler knows how to handle everything else.
For example (assuming a flags
keyword and that we're in the northern hemisphere),
flags enum MyFlags
{
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
Winter = January | February | March
Spring = April | May | June
Summer = July | August | September
Autumn = October | November | December
}
With this syntax, the compiler could create the 0 value itself, and assign flags to the other values automatically.
The attribute is [Flags] not [Flag] and there's nothing magical about it. The only thing it seems to affect is the ToString method. When [Flags] is specified, the values come out comma delimited. It's up to you to specify the values to make it valid to be used in a bit field.
There's nothing in the annotated C# 3 spec. I think there may be something in the annotated C# 4 spec - I'm not sure. (I think I started writing such an annotation myself, but then deleted it.)
It's fine for simple cases, but as soon as you start adding extra flags, it gets a bit tricky:
[Flags]
enum Features
{
Frobbing, // 1
Blogging, // 2
Widgeting, // 4
BloggingAndWidgeting = Frobbing | Blogging, // 6
Mindnumbing // ?
}
What value should Mindnumbing have? The next bit that isn't used? What about if you set a fixed integer value?
I agree that this is a pain. Maybe some rules could be worked out that would be reasonable... but I wonder whether the complexity vs value balance would really work out.
Simply put, Flags
is an attribute. It doesn't apply until after the enumeration is created, and thus doesn't change the values assigned to the enumeration.
Having said that, the MSDN page Designing Flags Enumerations says this:
Do use powers of two for a flags enumeration's values so they can be freely combined using the bitwise OR operation.
Important: If you do not use powers of two or combinations of powers of two, bitwise operations will not work as expected.
Likewise, the page for the FlagsAttribute
says
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.
In C, it's possible to (ab)use the preprocessor to generate power-of-two enumerations automatically. If one has a macro make_things which expands to "make_thing(flag1) make_thing(flag2) make_thing(flag3)" etc. it's possible to invoke that macro multiple times, with different definitions of make_thing, so as to achieve a power-of-two sequence of flag names as well as some other goodies.
For example, start by defining make_thing(x) as "LINEAR_ENUM_##x," (including the comma), and then use an enum statement to generate a list of enumerations (including, outside the make_things macro, LINEAR_NUM_ITEMS). Then create another enumeration, this time with make_thing(x) defined as "FLAG_ENUM_##x = 1<
Rather nifty some of the things that can be done that way, with flag and linear values automatically kept in sync; code can do nice things like "if (thingie[LINEAR_ENUM_foo] thing_errors |= FLAG_ENUM_foo;" (using both linear and flag values). Unfortunately, I know of no way to do anything remotely similar in C# or VB.net.
精彩评论