BrandGhost
C# Enum Flags: Combining Values with Bitwise Operations

C# Enum Flags: Combining Values with Bitwise Operations

04/28/2026(Updated 10/11/2026)

A standard enum lets one variable hold exactly one value. But sometimes a variable needs to represent a combination -- a user can have Read and Write access, not just one or the other. That is where C# enum flags come in.

The [Flags] attribute turns a regular enum into a bitfield. Instead of mutually exclusive choices, each member represents an independent bit that can be switched on or off. You combine members with bitwise OR, check individual members with bitwise AND, and get meaningful string output for free. This guide covers exactly how to set this up and where the pattern fits.

What Is a Flags Enum?

A flags enum is a regular C# enum decorated with the [Flags] attribute where each member is assigned a distinct power of two. Because powers of two have no overlapping bits, any combination of members produces a unique integer value:

None    = 0  = 0000
Read    = 1  = 0001
Write   = 2  = 0010
Execute = 4  = 0100

Combining Read | Write sets bits 0001 | 0010 = 0011 = 3. The combination is unique and fully reversible -- you can always extract which individual flags are set from the combined value.

Without [Flags], there is nothing technically preventing you from using bitwise operations on an enum, but general ToString() formatting returns a matching named constant when one exists and otherwise a numeric value, and tooling that inspects the attribute will miss the intent.

Declaring a Flags Enum

[Flags]
public enum FileAccess
{
    None      = 0,
    Read      = 1,            // 0001
    Write     = 2,            // 0010
    Execute   = 4,            // 0100
    ReadWrite = Read | Write  // 0011 -- composite convenience value
}

The rules for a correct flags enum:

  1. Include a None = 0 member. This represents "no flags set" and is important for default initialization and clearing.
  2. Assign power-of-two values for each independent flag.
  3. Define composite convenience values (like ReadWrite) using the named members, not raw integers. This way the composition is always expressed in terms of the individual flags.

A common mistake is using 1, 2, 3, 4:

// Wrong -- 3 collides with 1|2 and cannot be distinguished
[Flags]
public enum Permissions
{
    Read    = 1,
    Write   = 2,
    Execute = 3,  // Should be 4!
    Delete  = 4   // Should be 8!
}

With Execute = 3, you can never tell whether a variable holds "Execute" or "Read and Write." Always use powers of two for independent flags.

Setting and Combining Flags

Combine flags using bitwise OR:

// Grant Read and Execute access
FileAccess access = FileAccess.Read | FileAccess.Execute;
Console.WriteLine(access);    // "Read, Execute" (because [Flags] is present)
Console.WriteLine((int)access); // 5 (0001 | 0100 = 0101)

// Add a flag to an existing value
access |= FileAccess.Write;
Console.WriteLine(access);    // "ReadWrite, Execute" (source-derived; not executed here)

// Remove a flag
access &= ~FileAccess.Write;
Console.WriteLine(access);    // "Read, Execute"

The |= operator adds a flag; &= ~flag removes it. The tilde ~ is bitwise NOT, which flips all bits of the flag so that ANDing it clears only that bit.

Checking Whether a Flag Is Set

Two approaches: bitwise AND or HasFlag():

FileAccess access = FileAccess.Read | FileAccess.Execute;

// Bitwise AND -- explicit mask check
bool canRead    = (access & FileAccess.Read)    != 0;  // true
bool canWrite   = (access & FileAccess.Write)   != 0;  // false
bool canExecute = (access & FileAccess.Execute) != 0;  // true

// HasFlag -- readable all-bits check
bool alsoCanRead = access.HasFlag(FileAccess.Read);    // true

HasFlag is marked as an intrinsic in the fetched runtime source, but that annotation does not prove a particular optimization or performance result for every call. No benchmarks were run here; compare the forms in your own target runtime and workload if performance matters.

To check if all of a set of flags are present:

FileAccess required = FileAccess.Read | FileAccess.Write;
bool hasAll = (access & required) == required;

To check if any of a set of flags are present:

bool hasAny = (access & required) != 0;

Clearing and Toggling Flags

Practical bit manipulation patterns:

FileAccess access = FileAccess.Read | FileAccess.Write | FileAccess.Execute;

// Remove Write
access &= ~FileAccess.Write;
Console.WriteLine(access);  // "Read, Execute"

// Clear all flags
access = FileAccess.None;

// Toggle a flag (flip it regardless of current state)
access ^= FileAccess.Read;
Console.WriteLine(access);  // "Read" (was off, now on)
access ^= FileAccess.Read;
Console.WriteLine(access);  // "None" (was on, now off)

Toggling with XOR (^=) is less common but useful when you want to flip without checking the current state -- for example, implementing an "enable/disable" button in a UI.

ToString() and Parsing

With [Flags], ToString() can use a matching named constant or a comma-separated combination of names; bits that cannot be represented by the declared names can still produce a numeric value:

FileAccess access = FileAccess.Read | FileAccess.Execute;

Console.WriteLine(access.ToString());        // "Read, Execute"
Console.WriteLine(access.ToString("G"));     // "Read, Execute" (general)
Console.WriteLine(access.ToString("D"));     // "5" (decimal)
Console.WriteLine(access.ToString("F"));     // "Read, Execute" (flags, same as G for [Flags] enums)
Console.WriteLine(access.ToString("X"));     // "00000005" (hex)

Without [Flags], access.ToString() produces "5" for this unnamed value instead of "Read, Execute". The "F" format can decompose a value into names even without [Flags].

Parsing works the same way as regular enums:

// Parse a flags value from a string
if (Enum.TryParse<FileAccess>("Read, Execute", out FileAccess parsed))
{
    Console.WriteLine(parsed);  // "Read, Execute"
    Console.WriteLine((int)parsed);  // 5
}

// Parse a combined name defined as composite
if (Enum.TryParse<FileAccess>("ReadWrite", out FileAccess rw))
{
    Console.WriteLine(rw.HasFlag(FileAccess.Read));   // true
    Console.WriteLine(rw.HasFlag(FileAccess.Write));  // true
}

Using Long for Large Flags Enums

The default int underlying type provides 32 bits; however, the highest bit (1 << 31) produces a negative value due to signed integer representation. All 32 bit positions can represent independent flags, including the sign bit. Limiting an int enum to 31 flags is a policy for keeping values nonnegative, not a C# capacity limit. For more bit positions, use long:

[Flags]
public enum LargePermissions : long
{
    None         = 0L,
    Permission1  = 1L << 0,   // bit 0
    Permission2  = 1L << 1,   // bit 1
    // ...
    Permission63 = 1L << 62   // bit 62 (0-indexed, sign bit excluded by nonnegative-value policy)
}

The 1L << n syntax is clear and explicit. Writing 1 << 31 produces -2147483648 because the shift sets the sign bit of an int, not because of arithmetic overflow; use 1L << 31 for long-backed enums to avoid this.

In practice, an enum with more than 20-30 flags often indicates a design problem. Consider whether the flags can be grouped into smaller, focused enums.

When to Use Flags Enum vs Regular Enum

Scenario Flags Enum Regular Enum
Variable holds exactly one value No Yes
Variable holds zero or more values Yes No
Example: DayOfWeek No -- one day at a time Yes
Example: WorkingDays Yes -- Mon+Tue+Wed No
Example: HttpMethod No Yes
Example: FileAccess Yes No

A quick test: can your variable logically be "both X and Y at the same time"? If yes, flags. If no, regular.

For a full comparison of when to use enum versus other constructs, see When to Use Enum vs Constants in C# and the C# Enum Complete Guide.

Serializing Flags Enums

System.Text.Json with JsonStringEnumConverter serializes flags enums as comma-separated names:

using System.Text.Json;
using System.Text.Json.Serialization;

var options = new JsonSerializerOptions
{
    Converters = { new JsonStringEnumConverter() }
};

FileAccess access = FileAccess.Read | FileAccess.Execute;

string json = JsonSerializer.Serialize(access, options);
Console.WriteLine(json);  // "Read, Execute"

FileAccess deserialized = JsonSerializer.Deserialize<FileAccess>(json, options)!;
Console.WriteLine(deserialized.HasFlag(FileAccess.Read));    // true
Console.WriteLine(deserialized.HasFlag(FileAccess.Execute)); // true

When designing APIs, string enum serialization is preferred over integer serialization. It makes the payload self-documenting and prevents client code from depending on internal numeric values.

Real-World Example: User Permissions

A common real-world use for flags enum is a permission system. In this example, Can requires a nonzero mask and all requested bits; Can(None) returns false by this helper's policy, rather than inheriting HasFlag's true result for zero:

[Flags]
public enum UserPermission
{
    None         = 0,
    ViewContent  = 1 << 0,   // 1
    CreatePost   = 1 << 1,   // 2
    EditPost     = 1 << 2,   // 4
    DeletePost   = 1 << 3,   // 8
    ManageUsers  = 1 << 4,   // 16
    AdminAccess  = 1 << 5,   // 32

    // Convenience composites
    Contributor  = ViewContent | CreatePost,
    Editor       = Contributor | EditPost | DeletePost,
    Admin        = Editor | ManageUsers | AdminAccess
}

public class User
{
    public UserPermission Permissions { get; set; }

    public bool Can(UserPermission permission)
        => permission != UserPermission.None && Permissions.HasFlag(permission);
}

// Usage
var user = new User { Permissions = UserPermission.Contributor };

Console.WriteLine(user.Can(UserPermission.ViewContent));  // true
Console.WriteLine(user.Can(UserPermission.ManageUsers));  // false

// Grant extra permission
user.Permissions |= UserPermission.EditPost;
Console.WriteLine(user.Can(UserPermission.EditPost));     // true

// Revoke permission
user.Permissions &= ~UserPermission.EditPost;
Console.WriteLine(user.Can(UserPermission.EditPost));     // false

This is a clean, efficient permission model. Storage requires a single integer column; comparison is a bitwise operation; the composite convenience values (Editor, Admin) keep the code readable. For more context on how this integrates with How to Use Enum in C#, the foundational usage patterns apply directly.

Common Mistakes With Flags Enum

Not using powers of two. Values like 1, 2, 3, 4 produce ambiguous combinations. Use 1, 2, 4, 8 or the 1 << n syntax.

Forgetting None = 0. Zero still represents "no flags," and default(MyFlags) is an unnamed 0. Naming it None makes that state clearer; it does not change the underlying bitwise or HasFlag behavior.

Using == 0 instead of checking the None member. Semantically, access == FileAccess.None is clearer than (int)access == 0.

Using [Flags] on an enum with non-power-of-two values. The attribute is documentation and formatting guidance, not enforcement. C# will not stop you -- but ToString() will produce confusing output, and overlapping values produce defined bitwise results but cannot distinguish independent choices. Named composites remain useful when built from distinct independent bits.

For deeper context on common enum pitfalls across both regular and flags usage, see You're Using Enums Wrong and Enums in CSharp -- A Simple Guide to Expressive Code.

Frequently Asked Questions

What is the [Flags] attribute in C#?

[Flags] marks an enum as a bitfield where members can be combined using bitwise OR. It changes how ToString() renders combined values -- using a matching name or a combination of names when possible, otherwise the raw integer -- and signals design intent to readers.

How do I check if a flag is set in C#?

Use HasFlag(): myEnum.HasFlag(FlagValue). Or use bitwise AND: (myEnum & FlagValue) != 0. For a single nonzero flag bit, these checks agree. HasFlag(mask) checks that all requested bits are present and returns true for a zero mask; (value & mask) != 0 checks for any requested bit and returns false for zero. This correction package includes no benchmark evidence establishing equivalent performance between HasFlag and the bitwise form.

How do I add and remove flags from a C# enum?

Add with bitwise OR: access |= FileAccess.Write;. Remove with bitwise AND and NOT: access &= ~FileAccess.Write;. Toggle with XOR: access ^= FileAccess.Write;.

Why must enum flags be powers of two?

Powers of two have no overlapping bits, so any combination of flags produces a unique integer. Non-power-of-two values overlap with combinations of smaller values, making it impossible to determine which individual flags are set from the combined result.

Can I use a long for a C# flags enum?

Yes. Add : long after the enum name and use 1L << n syntax for flag values. A long has 64 bit positions, including its sign bit; 63 is the limit only if you choose nonnegative values. An int similarly has 32 positions. uint and ulong are alternatives when you want nonnegative numeric values while using all 32 or 64 bits.

How do I serialize a flags enum to JSON in C#?

Add JsonStringEnumConverter to your JsonSerializerOptions. The serializer renders combined flags as a comma-separated string of names, which is more readable than the raw integer.

When should I use [Flags] vs a regular enum?

Use [Flags] when a variable must logically hold more than one value at a time (permissions, weekday selection, feature toggles). Use a regular enum when the variable holds exactly one exclusive choice (status, severity, direction).

C# Enum: Complete Guide to Enumerations in .NET

Master C# enum with this complete guide. Learn to declare enums, use flags, convert to string, apply switch pattern matching, and follow .NET best practices.

How to Use Enum in C#: Declaration, Values, and Best Practices

Learn how to use enum in C# from scratch. Covers declaring enums, assigning values, casting, comparing, iterating, and the best practices every C# developer should follow.

C# Enum to String: Conversion Patterns and Best Practices

Learn every C# enum to string conversion pattern: ToString, Enum.GetName, JsonStringEnumConverter, custom display names, and parsing strings back to enums safely.

An error has occurred. This application may no longer respond until reloaded. Reload