A Brief Discussion on the Application and Insights of Flag Enums
What is an Enum
Before discussing Flag Enums, let's first understand what an Enum (Enumeration type) is. An Enum is a value type composed of a set of named integer constants. By default, the underlying type of the enumeration items is int, but it can be specified as other integer types. If the value of an enumeration item is not specified, it starts from 0 and increments by one.
In software development, it is common to use a set of constants to represent specific states, types, or operations, especially when used as method parameters. While using numeric values can reduce the possibility of input errors, it is not intuitive to identify the specific meaning of each value. Although using meaningful strings can reduce the possibility of string input errors, and defining constants for each value individually helps improve this, it still cannot prevent the use of other undefined values.
Through Enums, we can assign meaningful names to each enumeration item, which improves code readability while preventing the possibility of using undefined values, thereby increasing the stability of the program. Even for developers who are not familiar with English, if they do not understand the naming of the Enum items, they can still clearly understand the meaning of each item through the comments displayed by the editor.
The following is an example of an Enum:
enum Action : ushort { // Specify the member type as ushort; defaults to int if not specified
/// <summary>
/// None(0)
/// </summary>
None, // No value set, starts from 0
/// <summary>
/// Query(1)
/// </summary>
Query, // Value is 1
/// <summary>
/// Create(10)
/// </summary>
Create = 10,
/// <summary>
/// Update(11)
/// </summary>
Update, // Value is 11 because the previous item was set to 10
/// <summary>
/// Delete(12)
/// </summary>
Dalete // Value is 12
}
Action action = Action.Update;
string name = action.ToString(); // name = "Update"
ushort value = (ushort)action; // value = 11Introduction to Flag Enums
A Flag Enum can be viewed as a set of Enums that support bitwise operations, where each enumeration value represents an independent flag. By combining these flags, you can effectively represent multiple states or options.
How to Define a Flag Enum
A Flag Enum is defined by adding the FlagsAttribute to the Enum and using powers of 2 to define each enumeration value. Other values are composite values, and you can also define your own composite enumeration items.
The following is an example:
[Flags]
enum Permissions {
None = 0,
CanQuery = 1,
CanCreate = 2,
CanUpdate = 4,
CanDelete = 8,
CanUpsert = CanCreate | CanUpdate, // Value is 6
ExcludeDelete = ~CanDelete, // Value is -9
All = CanQuery | CanCreate | CanUpdate | CanDelete
}
// Directly using a defined composite value, ToString() returns the name of that value
Permissions permission = Permissions.CanUpsert;
string name = permission.ToString(); // name = "CanUpsert"
int value = (int)permission; // value = 6
// Using bitwise operations, the result is a defined composite value, ToString() returns the name of that value
permission = Permissions.CanCreate | Permissions.CanUpdate;
name = permission.ToString(); // name = "CanUpsert"
value = (int)permission; // value = 6
// Using bitwise operations, the result is a non-defined composite value, ToString() returns the names of each enumeration value
permission = Permissions.CanCreate | Permissions.CanDelete;
name = permission.ToString(); // name = "CanCreate, CanDelete"
value = (int)permission; // value = 10
// Using the ~ bitwise operation, the result is a defined composite value, ToString() returns the name of that value
permission = ~Permissions.CanDelete;
name = permission.ToString(); // name = "ExcludeDelete"
value = (int)permission; // value = -9
// Using the ~ bitwise operation, the result is a non-defined composite value, ToString() returns the numeric value
permission = ~Permissions.CanCreate;
name = permission.ToString(); // name = "-3"
value = (int)permission; // value = -3Or use bitwise operations to define the values:
enum Permissions {
None = 0,
CanQuery = 1 << 0,
CanCreate = 1 << 1,
CanUpdate = 1 << 2,
CanDelete = 1 << 3
}Microsoft recommends using plural names for Flag Enum types, such as RegexOptions; and for general Enum types, such as DayOfWeek.
How to Use Flag Enums
Flag Enums can effectively simplify method parameters, making them more readable. The following case is very suitable for refactoring using a Flag Enum:
void Execute(bool canQuery, bool canCreate, bool canUpdate, bool canDelete) {
// Actual execution logic
}The approach using a Flag Enum:
void Execute(Permissions permiss) {
// Actual execution logic
}This refactoring makes the method parameters clearer and eliminates the confusion that can arise when using multiple boolean values. Using a Flag Enum not only improves readability but also makes it more convenient to extend permissions in the future.
Bitwise Operations
The following uses the concept of sets to explain bitwise operations related to Flag Enums.
- OR (
|) operator: Performs an OR operation on two enumeration values to form a set containing both enumeration items, i.e., a union.

- AND (
&) operator: Performs an AND operation on two enumeration values to form a set containing the overlapping enumeration items, i.e., an intersection.

- XOR (
^) operator: Performs an XOR operation on two enumeration values to form a set that does not contain the overlapping items, i.e., a symmetric difference.

- NOT (
~) operator: Performs a NOT operation on an enumeration value to produce a set of enumeration values that does not contain the original items, i.e., a complement.

There is no difference operator for bitwise operations, so you cannot simply remove a specific enumeration item. However, you can achieve the same effect in the following ways:
- First, get the complement of the item to be removed, then perform an intersection with the original item. The code is
Permissions.CanUpsert & ~Permissions.CanCreate. - First, form a union with the item to be removed, then perform a symmetric difference with the item to be removed. The code is
(Permissions.CanUpsert | Permissions.CanCreate) ^ Permissions.CanCreate.
Determining Whether a Specific Enumeration Value is Included
If you want to determine whether a specific enumeration item is included, you can use the following methods:
- Perform an intersection with the target enumeration item, then check if the result is equal to the target item.
// has1 = true
bool has1 = (Permissions.CanUpsert & Permissions.CanCreate) == Permissions.CanCreate;
// has2 = false
bool has2 = (Permissions.CanUpsert & Permissions.CanDelete) == Permissions.CanDelete;
// has3 = true
bool has3 = (Permissions.CanUpsert & Permissions.None) == Permissions.None;
// has4 = false
bool has4 = (Permissions.ExcludeDelete & Permissions.CanDelete) == Permissions.CanDelete;
// has5 = true
bool has5 = (Permissions.ExcludeDelete & Permissions.CanCreate) == Permissions.CanCreate;
// has6 = false
bool has6 = (Permissions.All & Permissions.ExcludeDelete) == Permissions.ExcludeDelete;HasFlag: A method added to Enum in .NET Framework 4.0. Internally, it still uses the logic described above. The results are as follows:
// has1 = true
bool has1 = Permissions.CanUpsert.HasFlag(Permissions.CanCreate);
// has2 = false
bool has2 = Permissions.CanUpsert.HasFlag(Permissions.CanDelete);
// has3 = true
bool has3 = Permissions.CanUpsert.HasFlag(Permissions.None);
// has4 = false
bool has4 = Permissions.ExcludeDelete.HasFlag(Permissions.CanDelete);
// has5 = true
bool has5 = Permissions.ExcludeDelete.HasFlag(Permissions.CanCreate);
// has6 = false
bool has6 = Permissions.All.HasFlag(Permissions.ExcludeDelete);Concerns Regarding Bitwise Operations
In the examples above, you might have questions about Permissions.CanUpsert.HasFlag(Permissions.None) and Permissions.All.HasFlag(Permissions.ExcludeDelete).
First, from the perspective of bitwise operations, Permissions.CanUpsert & Permissions.None == Permissions.None being true is correct. From the perspective of sets, None represents an empty set, and an empty set is a subset of any set. Even if None is defined, the result is as shown in the figure below:

Rather than the figure below:

The concerns regarding Permissions.All.HasFlag(Permissions.ExcludeDelete) mainly stem from the naming of All and a potential misunderstanding of ExcludeDelete. Although named All, it actually only contains the defined enumeration items. In other words, if a new enumeration item is defined but the value of All is not updated, it will not contain the new definition. Therefore, for Enums that may be extended, you should be cautious when using the name All. ExcludeDelete is defined using the NOT (~) operator; it consists of defined enumeration items that do not include Delete and undefined values. This is shown in the orange area in the figure below:

Therefore, the result of Permissions.All.HasFlag(Permissions.ExcludeDelete) is false. This also explains why (~Permissions.CanCreate).ToString() returns a numeric value rather than the names of the contained enumeration items. Thus, it is recommended to avoid using the NOT (~) operator to define composite enumeration items.
Change Log
- 2023-12-05 Initial version of the document created.
