In this article, we are going to learn about the Flags attribute for enum in C#, what its benefits are, and how to use it in a practical example.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Define the Problem

Let’s define an enum that represents the types of users:

public enum UserType
{
    Customer = 1,
    Driver = 2,  
    Admin = 3,
}

We define the UserType enum that contains three values: Customer, Driver, and Admin.

But what if we need to represent a collection of values?

For example, at a delivery company, we know that both the Admin and the Driver are employees. So let’s add a new enumeration item Employee. Later on, we will show you how we can represent both the admin and the driver with it:

public enum UserType
{   
    Customer = 1,
    Driver = 2,  
    Admin = 3,
    Employee = 4
}

Define and Declare a Flags Attribute

 A Flags is an attribute that allows us to represent an enum as a collection of values ​​rather than a single value. So, let’s see how we can implement the Flags attribute on enumeration:

[Flags]
public enum UserType
{   
    Customer = 1,
    Driver = 2,  
    Admin = 4,
}

We add the Flags attribute and number the values with powers of 2. Without both, this won’t work.

Now going back to our previous problem, we can represent Employee using the | operator:

var employee = UserType.Driver | UserType.Admin;

Also, we can define it as a constant inside the enum to use it directly:

[Flags]
public enum UserType
{                
    Customer = 1,             
    Driver = 2,               
    Admin = 4,                
    Employee = Driver | Admin
}

Behind the Scenes

To understand the Flags attribute better, we must go back to the binary representation of the number. For example, we can represent 1 as binary 0b_0001 and 2 as 0b_0010:

[Flags]
public enum UserType
{
    Customer = 0b_0001,
    Driver = 0b_0010,
    Admin = 0b_0100,
    Employee = Driver | Admin, //0b_0110
}

We can see that each value is represented in an active bit. And this is where the idea of ​​numbering the values ​​with the power of 2 came from. We can also note that Employeecontains two active bits, that is, it is a composite of two values Driver and Admin.

Operations on Flags Attribute

We can use the bitwise operators to work with Flags.

Initialize a Value

For the initialization, we should use the value 0 named None, which means the collection is empty:

[Flags]
public enum UserType
{
    None = 0,
    Customer = 1,
    Driver = 2,
    Admin = 4,
    Employee = Driver | Admin
}

Now, we can define a variable:

var flags = UserType.None;

Add a Value 

We can add value by using | operator:

flags |= UserType.Driver;

Now, the  flags variable equals Driver.

Remove a Value

We can remove value by use &, ~ operators:

flags &= ~UserType.Driver;

Now, flagsvariable equals None.

Check if the Value Exists

We can check if the value exists by using & operator:

Console.WriteLine((flags & UserType.Driver) == UserType.Driver);

The result is False.

Also, we can do this by using the HasFlag method:

Console.WriteLine(flags.HasFlag(UserType.Driver));

Also, the result will be False.

As we can see, both ways, using the & operator and the HasFlag method, give the same result, but which one should we use? To find out, we will test the performance on several frameworks.

Measure the Performance

First, we will create a Console App, and in the .csproj file we will replace the TargetFramwork tag with the TargetFramworks tag:

<TargetFrameworks>net48;netcoreapp3.1;net6.0</TargetFrameworks>

We use the TargetFramworks tag to support multiple frameworks: .NET Framework 4.8, .Net Core 3.1, and .Net 6.0.

Secondly, let’s introduce the BenchmarkDotNet library to get the benchmark results:

[Benchmark]
public bool HasFlag()
{
    var result = false;
    for (int i = 0; i < 100000; i++)
    {
        result = UserType.Employee.HasFlag(UserType.Driver);
    }

    return result;
}

[Benchmark]
public bool BitOperator()
{
    var result = false;
    for (int i = 0; i < 100000; i++)
    {
        result = (UserType.Employee & UserType.Driver) == UserType.Driver;
    }

    return result;
}

We add [SimpleJob(RuntimeMoniker.Net48)], [SimpleJob(RuntimeMoniker.NetCoreApp31)], and [SimpleJob(RuntimeMoniker.Net60)] attributes to the HasFlagBenchmarker class to see the performance differences between different versions of .NET Framework / .NET Core:

|      Method |                Job |            Runtime |        Mean |      Error |      StdDev |      Median |
|------------ |------------------- |------------------- |------------:|-----------:|------------:|------------:|
|     HasFlag |           .NET 6.0 |           .NET 6.0 |    37.79 us |   3.781 us |    11.15 us |    30.30 us |
| BitOperator |           .NET 6.0 |           .NET 6.0 |    38.17 us |   3.853 us |    11.36 us |    30.38 us |
|     HasFlag |      .NET Core 3.1 |      .NET Core 3.1 |    38.31 us |   3.939 us |    11.61 us |    30.37 us |
| BitOperator |      .NET Core 3.1 |      .NET Core 3.1 |    38.07 us |   3.819 us |    11.26 us |    30.33 us |
|     HasFlag | .NET Framework 4.8 | .NET Framework 4.8 | 2,893.10 us | 342.563 us | 1,010.06 us | 2,318.93 us |
| BitOperator | .NET Framework 4.8 | .NET Framework 4.8 |    38.04 us |   3.920 us |    11.56 us |    30.17 us |

So, in .NET Framework 4.8HasFlag method was much slower than the BitOperator. But, the performance improves in .Net Core 3.1 and .Net 6.0. So in newer versions, we can use both ways.

A Practical Example of Flags Attribute

Let’s say we want to send messages to users by their type.

We can define previous operations as Extension Methods:

public static UserType Add(this UserType userType, params UserType[] typesToAdd)
{
    foreach (var flag in typesToAdd)
    {
        userType |= flag;
    }

    return userType;
}

public static UserType Remove(this UserType userType, params UserType[] typesToRemove)
{
    foreach (var item in typesToRemove)
    {
        userType &= ~item;
    }

    return userType;
}

public static bool CustomHasFlag(this UserType userType, UserType typeToCompare)
    => (userType & typeToCompare) == typeToCompare;

public static void Print(this UserType userType)
    => Console.WriteLine("This message is for users of type: {0}.", userType);

Now, we can use them easily:

var audience = UserType.None;

audience = audince.Add(UserType.Employee, UserType.Customer);
audience.Print();

audience = audience.Remove(UserType.Driver);
audience.Print();

if (audience.CustomHasFlag(UserType.Driver)) // or use HasFlag
{
    Console.WriteLine("Driver exists.");
}
else
{
    Console.WriteLine("Driver doesn't exist.");
    Console.WriteLine("Adding Driver...");
    audience = audience.Add(UserType.Driver);
}
audience.Print();

And the output will be:

This message is for users of type: Customer, Employee.
This message is for users of type: Customer, Admin.
Driver doesn't exist.
Adding Driver...
This message is for users of type: Customer, Employee.

Conclusion

In this article, we’ve learned about the Flags attribute in C# and operations we can do with it. We’ve also compared two ways to check if a value exists. Finally, we’ve used our knowledge in a single example to see the Flags attribute in action.