Back to Blog
C#

Using Flags Enums in C#

c# flags enum: Learn how to design and use flags enums in C# to combine multiple options efficiently with bitwise operations, including parsing, validation, and common...

EnumsBitwise OperationsC#FlagsAttributeEnum.HasFlag
Illustration of a C# flags enum where binary bits are combined using bitwise operations.

When you need to represent a set of boolean options in a single value, a c# flags enum is the standard tool. The [Flags] attribute changes how enum values are treated, allowing you to combine them with bitwise OR and test for presence with bitwise AND. Without the attribute, enums behave as single-value types and the framework does not provide convenient string formatting for combinations. The pattern is especially common in APIs that accept a set of toggles, such as file access modes or notification preferences.

Declaring a Flags Enum

A flags enum is an enum decorated with the [Flags] attribute, and its members are typically assigned powers of two.

[Flags] public enum FileAccess { None = 0, Read = 1, Write = 2, Execute = 4, Delete = 8 }

Values must not overlap in their binary representation. If they do, combining them with OR will produce an ambiguous result. For example, if Read were 1 and Write were also 1, setting both would produce the same value as setting either one alone. The base-2 pattern ensures each combination has a unique numeric value.

The None = 0 member is conventional. It allows an explicit absence of flags and matches the default value of an enum variable. Avoid defining a member with a zero value that represents a real option, because zero cannot be detected as a flag when combined with others.

Combining and Testing Flags

Once defined, you combine flags with the bitwise OR operator (|) and test presence with the bitwise AND operator (&).

FileAccess access = FileAccess.Read | FileAccess.Write; bool canRead = (access & FileAccess.Read) == FileAccess.Read; bool canDelete = (access & FileAccess.Delete) == FileAccess.Delete; Console.WriteLine(access); // Output: Read, Write Console.WriteLine(canRead); // True Console.WriteLine(canDelete); // False

The HasFlag method provides a more readable alternative:

bool canWrite = access.HasFlag(FileAccess.Write);

HasFlag returns true when the specified flag is present. Internally it performs the same bitwise AND operation. In most application code the readability benefit outweighs anything else, but be aware that HasFlag is slightly slower than a manual AND check because it boxes the value and performs additional work. For hot loops or extremely high-frequency checks, a manual (value & flag) == flag is faster. That said, for typical business logic with thousands of calls per second the difference is negligible.

Formatting and Parsing Flag Combinations

The ToString() method on a flags enum returns a comma-separated list of the names of the set flags. That is useful for logging and user-facing messages.

FileAccess access = FileAccess.Read | FileAccess.Delete; Console.WriteLine(access.ToString()); // Read, Delete

Parsing the string back into an enum uses Enum.Parse or TryParse. Both accept comma-separated names and combine them automatically.

string input = "Read, Write"; if (Enum.TryParse(input, out FileAccess access)) { // access is FileAccess.Read | FileAccess.Write }

A caveat: TryParse also accepts numeric strings. If you pass "5", it returns a value that corresponds to Read | Write even though no member is named 5. That behavior can be intentional for deserialization, but it can also silently accept invalid combinations if you expect names only.

Validation: Is the Combination Defined?

A flags enum value may contain any combination of bits, but not every combination corresponds to a defined member. For instance, FileAccess.Read | FileAccess.Delete is valid and set, but there is no single member with that value. Sometimes you want to ensure that a given value only uses valid flags, or that it matches a named member exactly.

To reject undefined combinations, check that all bits are accounted for:

public static bool IsValid(FileAccess value) { int validBits = 0; foreach (FileAccess flag in Enum.GetValues<FileAccess>()) { validBits |= (int)flag; } return (value & ~validBits) == 0; }

The expression ~validBits flips the bits that are defined, so value & ~validBits yields the bits that are set in value but not defined. If any are set, the value contains invalid flags.

Be careful with the All pattern. A common but flawed validation is:

if (access == (FileAccess.Read | FileAccess.Write | FileAccess.Execute))

That only matches the exact combination, not all flags combined. For a large enum, writing every member manually is maintenance-heavy. Instead, you can derive an All value by OR-ing all members, but that requires reflection if done at runtime. A static readonly field works well when the enum is known at compile time:

[Flags] public enum FileAccess { None = 0, Read = 1, Write = 2, Execute = 4, Delete = 8 } public static class FileAccessHelper { public const FileAccess All = FileAccess.Read | FileAccess.Write | FileAccess.Execute | FileAccess.Delete; }

Using const allows the value to be evaluated at compile time, which avoids any runtime overhead.

Removing a Flag and Toggle Behavior

Removing a flag is not as straightforward as adding one, because you cannot simply AND with the inverse of the flag unless you are working with a limited bit width. The correct way is to combine the flag with a bitwise complement of the specific flag:

access &= ~FileAccess.Write;

This clears the Write bit while preserving all others. The complement operator ~ flips all bits of the flag, so the result has zeros only in the positions that match the flag.

If you need to toggle a flag based on a boolean condition, use an XOR:

access ^= FileAccess.Read;

XOR flips the flag if it was present, and adds it if it was not. This is useful for UI checkboxes where the user checks or unchecks an option.

When Not to Use Flags

A flags enum is not always the right choice. If you feel the need to name every possible combination, such as ReadAndWrite, ReadAndExecute, or All, that is a signal that you should consider a different design. Named combinations are brittle because adding a new flag requires adding many new combinations.

The flag pattern is best suited for independent options that can be combined arbitrarily. For example, a notification setting might include Email, Sms, and Push. Those are independent toggles. But if options are mutually exclusive, such as color choices, a regular enum with a single value is appropriate.

Performance is rarely the reason to choose flags. The bitwise operations are fast, but the cost of an enum is not inherently better than a boolean field or a HashSet<Enum>. The real advantage is expressiveness and compact storage: a flags enum packs multiple booleans into a single integer, which is useful for saving to a database column or a serialized message. If your application passes these values across network or storage boundaries, the compact representation can be beneficial.

Runtime Cost and Allocations

The main runtime costs of flags enums come from boxing and string formatting. Enum values are value types, and calling HasFlag boxes the argument because the method takes an Enum parameter. In tight loops, that allocation can produce garbage collection pressure. The manual bitwise test avoids boxing entirely.

String formatting, such as ToString(), allocates a new string every time. If you format flags frequently in logs, consider caching the formatted string or using the numeric value for logging when exact symbol names are not essential.

The Enum.GetValues<T>() method introduced in .NET 5 can be used to iterate over named members efficiently. Before that, Enum.GetValues(typeof(T)) allocated an array. For a small enum the difference is small, but in hot paths the generic version is preferable.

Parsing Pitfalls with Comma-Separated Values

Enum.TryParse handles comma-separated names by OR-ing the parsed values. However, it will also parse a numeric string that may represent an undefined combination. The method does not throw when the resulting value is not a defined member; it returns a value with the given bits set. That behavior is rarely a problem if you are parsing data you control, but when accepting user input you should validate the result with a check like IsValid described earlier.

Another subtlety: the parsing is case-sensitive by default. If your input may be in different cases, pass ignoreCase: true to the parse method. That makes the parsing more forgiving but adds a tiny overhead.

Combining Flags with Bitwise Operators: Complete Example

The following example demonstrates a complete workflow: definition, combination, testing, removal, and validation.

[Flags] public enum NotificationOptions { None = 0, Email = 1, Sms = 2, Push = 4 } class Program { static void Main() { NotificationOptions options = NotificationOptions.Email | NotificationOptions.Sms; // Test presence bool sendEmail = options.HasFlag(NotificationOptions.Email); bool sendPush = (options & NotificationOptions.Push) == NotificationOptions.Push; // Remove Sms options &= ~NotificationOptions.Sms; // Toggle Push options ^= NotificationOptions.Push; // Parse from string if (Enum.TryParse<NotificationOptions>("Email, Sms", ignoreCase: true, out var parsed)) { Console.WriteLine(parsed); // Email, Sms } } }

In this code, options starts as Email | Sms. The HasFlag check returns true for Email. The manual check for Push returns false. Removing Sms leaves Email only. The XOR toggles Push, so options becomes Email | Push. Finally, the parse example demonstrates how to convert a comma-separated string into a flags value.

Maintainability and Design Tradeoffs

The biggest maintainability concern with flags enums is the numeric values. If you insert a new member in the middle of an enum without skipping powers of two, you break existing stored values. For example, adding Create = 3 between Write = 2 and Execute = 4 would overlap with the Write bit, causing all combinations involving Write to include Create as well. Always assign explicit powers of two and never assign a member a value that is a combination of others.

Another tradeoff is the readability of numeric literals. Using 1 << 0, 1 << 1, and so on makes the pattern explicit but less immediately readable. Explicit constants like Read = 1, Write = 2 are easier for most developers to verify. Consistency across the team matters more than which syntax you choose.

Finally, consider the serialization behavior. By default, the numeric value is what gets stored when you save to a database or JSON. If you later change the enum's numeric alignment, existing data will be misinterpreted. To mitigate this, treat the numeric values as part of your public contract. Document that the numeric layout must remain stable, and review changes to flags enums in code reviews as a compatibility risk.

c# flags enum: Practical Usage and Code Examples | RYUSLOG DEV