Flag Enums
Powers of two plus [Flags] let one value carry several options at once, and four bitwise operators set, test, clear and toggle them.
[Flags] enum Permissions { None = 0, Read = 1, Write = 2, Delete = 4 }Some options are not a single choice. A user may have read and write but not delete. A flag enum packs several yes-or-no options into one value. Each member is given a different power of two: 1, 2, 4, 8. Written in binary, each of those numbers has exactly one digit set, so no combination of them can be mistaken for another. One binary digit is called a bit.
You then mark the enum with [Flags]. An attribute is a marker in square brackets written above a declaration, carrying information other code can read. [Flags] changes no arithmetic at all, and it changes no parsing either: Enum.Parse and Enum.TryParse accept a comma-separated list of names for any enum, marked or not. All the attribute does is tell ToString to print Read, Write instead of 3.
The four operations
| Goal | Code |
|---|---|
| Combine | a | b |
| Test | (x & a) == a, or x.HasFlag(a) |
| Clear | x & ~a |
| Toggle | x ^ a |
x.HasFlag(a) is documented as returning exactly (x & a) == a, so the two forms are the same test written two ways.
What trips people up
Nothing checks that your numbers are powers of two. Write Delete = 3 by accident and Delete silently means read-plus-write. Always add a None = 0 member for "nothing set", and test for it with == rather than HasFlag.
Example
Permissions granted = Permissions.Read | Permissions.Write;
Console.WriteLine(granted); // [Flags] makes this readable
Console.WriteLine((int)granted); // 1 + 2
// Two ways to write the same test.
Console.WriteLine(granted.HasFlag(Permissions.Write));
Console.WriteLine((granted & Permissions.Write) == Permissions.Write);
// Add one bit, then clear another.
granted |= Permissions.Delete; // same as granted = granted | Permissions.Delete
granted &= ~Permissions.Read; // same as granted = granted & ~Permissions.Read
Console.WriteLine(granted);
[Flags]
enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Delete = 4
}
// Output:
// Read, Write
// 3
// True
// True
// Write, DeleteWhen to use it
- Store a user's permissions on a record in one integer column.
- Record which notification channels a subscriber chose from one set of checkboxes.
- Pass a combination of options into a library method that takes a flags parameter.
- Test whether a request asks for any of several capabilities at once.
More examples
Shifts and a named combination
1 << n means "1 shifted n places left in binary", which is the n-th power of two. It is longer to read than 1, 2, 4 but impossible to get wrong when the list grows to sixteen members. Note what ToString does: 7 matches the member All exactly, so it prints All, while 3 matches no single member and is spelled out. Use a named combination for a set you grant together, such as a role.
Console.WriteLine((int)FileAccessMode.All);
Console.WriteLine(FileAccessMode.All);
// Start from everything and take one option away.
FileAccessMode mode = FileAccessMode.All & ~FileAccessMode.Execute;
Console.WriteLine(mode);
[Flags]
enum FileAccessMode
{
None = 0,
Read = 1 << 0, // 1, the same as writing Read = 1
Write = 1 << 1, // 2, the same as Write = 2
Execute = 1 << 2, // 4, the same as Execute = 4
// A member may be a combination of others.
All = Read | Write | Execute // 7
}
// Output:
// 7
// All
// Read, WriteWhat [Flags] actually changes
Both enums combine with | and both produce the value 3. Only the marked one knows how to describe itself. That is worth knowing when you are reading someone else's code and see a bare number in a log line or an error message: the enum was missing [Flags], not the bits.
// The attribute changes printing and parsing, not arithmetic.
Console.WriteLine(WithFlags.Read | WithFlags.Write);
Console.WriteLine(WithoutFlags.Read | WithoutFlags.Write);
[Flags]
enum WithFlags
{
None = 0,
Read = 1,
Write = 2
}
enum WithoutFlags
{
None = 0,
Read = 1,
Write = 2
}
// Output:
// Read, Write
// 3Testing for any match, and toggling
HasFlag only answers "are all of these set". For "is at least one of these set" you need the & and != 0 form, and the literal 0 is allowed on the right of a comparison with any enum. The ^ operator is what a checkbox in a settings screen does: it turns one option on or off without disturbing the rest.
Notifications subscribed = Notifications.Email | Notifications.Sms;
Notifications urgent = Notifications.Sms | Notifications.Push;
// "Any of these" is != 0. "All of these" would be == urgent.
bool anyUrgent = (subscribed & urgent) != 0;
Console.WriteLine(anyUrgent);
// ^ flips exactly the bits that are set on the right.
subscribed ^= Notifications.Email;
Console.WriteLine(subscribed);
subscribed ^= Notifications.Email;
Console.WriteLine(subscribed);
[Flags]
enum Notifications
{
None = 0,
Email = 1,
Sms = 2,
Push = 4
}
// Output:
// True
// Sms
// Email, Sms
Discussion