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.

Syntax[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

GoalCode
Combinea | b
Test(x & a) == a, or x.HasFlag(a)
Clearx & ~a
Togglex ^ 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

Example · csharp
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, Delete

When 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.

Example · csharp
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, Write

What [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.

Example · csharp
// 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
// 3

Testing 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.

Example · csharp
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

  • Be the first to comment on this lesson.