Enumeration are commonly used to define a set of mutually exclusive values (ex: colors, days of the week, months, etc.).
When working with them, we often want to check that a given value is part of a subset of our enumeration. Unfortunately, it may result in verbose conditions or in several of them being quite long and prone to bugs due to their length.
Hopefully, .NET has a pretty neat feature that can help you having smarter and cleaner conditions in such cases: let's explore it!
Use case
For our use case, let's create a DaysOfTheWeek
enum with all of them:
public enum DaysOfTheWeek
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
We then want to compute the price of a given service depending of the day of the week.
Our algorithm is not very hard but has a couple of conditions:
- If the provided day is in the week-end, it is
3
- If the provided day is Monday, Tuesday or Friday, it is
1
because those are counted as special days by the company - Otherwise, the price will just be
2
A first implementation of those rules might be:
public int GetRate(DaysOfTheWeek day)
{
if (day == DaysOfTheWeek.Saturday
|| day == DaysOfTheWeek.Sunday)
{
return 3;
}
if (day == DaysOfTheWeek.Monday
|| day == DaysOfTheWeek.Tuesday
|| day == DaysOfTheWeek.Friday)
{
return 1;
}
return 2;
}
There are, however, some disadvantages to such algorithm:
- The method looks pretty "complicated" for three simple conditions (many ORs and a lot of chars)
- The logic we implemented is not very explicit and having someone looking at the code will certainly result in questions asking how are those days chosen
Introducing the Flags
attribute
With the [Flags]
attribute, we can use our enum values as flags an perform bitwise comparison on them without too much pain
To do so, we only have to slightly modify our enumeration by:
- Adding the
[Flags]
attribute to it - Assigning each enumeration constant a power of two so that they will not overlap each other when combining them
Doing so will result in the following code:
[Flags]
public enum DaysOfTheWeek
{
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64,
}
By just updating our enumeration, we didn't altered our code and the previous function still works great. However, let's now use our flags for the conditions using the HasFlag
method:
public int GetRate(DaysOfTheWeek day)
{
if ((DaysOfTheWeek.Saturday | DaysOfTheWeek.Sunday).HasFlag(day))
{
return 3;
}
if ((DaysOfTheWeek.Monday | DaysOfTheWeek.Tuesday | DaysOfTheWeek.Friday).HasFlag(day))
{
return 1;
}
return 2;
}
This is still very verbose and does not solve anything right away but one interesting thing about flags is that we can define values in our enumeration that are a combination of others.
In our case, by having a dedicated value for the weekend and another one for the special days, we might succeed in improving the overall readability a little bit (pun intended), let's do that:
[Flags]
public enum DaysOfTheWeek
{
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64,
+ Weekend = Saturday | Sunday,
+ SpecialDay = Monday | Tuesday | Friday,
}
By introducing those values, our code is suddenly much straightforward but also self-explanatory as for why there is such conditions:
public int GetRate(DaysOfTheWeek day)
{
if (DaysOfTheWeek.Weekend.HasFlag(day))
{
return 3;
}
if (DaysOfTheWeek.SpecialDay.HasFlag(day))
{
return 1;
}
return 2;
}
Take aways
Using flags, we were able to combine several enumeration constants onto a single one to clarify the intent of our algorithm by only slightly altering it
However, flags might not always be the solution for those problems: if you have only a small subset of constants that you want to check a value against, it might not be worth the complexity
Similarly, introducing flags to an enumeration that is persisted in a database will require you to update all your previous stored records
Finally, you should be careful about the powers of two that you are using: a wrong value or an overlapping one might introduce nasty bugs
As a side note, be very careful when using flags for access rights not to grant anything for the value
0
as0.HasFlag(/*anything*/)
will returntrue
Additional tip
As a side note, it may be too verbose to write the powers of two yourself. You can use the shift-left operator to let the computer do that for you!
[Flags]
public enum DaysOfTheWeek
{
Monday = 1<<0, // 1
Tuesday = 1<<1, // 2
Wednesday = 1<<2, // 4
Thursday = 1<<3, // 8
Friday = 1<<4, // 16
Saturday = 1<<5, // 32
Sunday = 1<<6, // 64
Weekend = Saturday | Sunday,
SpecialDay = Monday | Tuesday | Friday,
}
I hope that you learned something useful, happy coding!
Top comments (0)