Using Flagged Enums
Have you ever written or come across code where you have a series of boolean values that determine whether or not certain actions are allowed?
Example:
public class WorkDays
{
public bool Sunday { get; set; }
public bool Monday { get; set; }
public bool Tuesday { get; set; }
public bool Wednesday { get; set; }
public bool Thursday { get; set; }
public bool Friday { get; set; }
public bool Saturday { get; set; }
public bool Weekdays { get { return Monday && Tuesday && Wednesday && Thursday && Friday; } }
public bool Weekends { get { return Sunday && Saturday; } }
public int NumberOfDaysWorked
{
get
{
int numberOfDaysWorked = 0;
if (CanWorkMonday) numberOfDaysWorked++;
if (CanWorkTuesday) numberOfDaysWorked++;
if (CanWorkWednesday) numberOfDaysWorked++;
if (CanWorkThursday) numberOfDaysWorked++;
if (CanWorkFriday) numberOfDaysWorked++;
if (CanWorkSaturday) numberOfDaysWorked++;
if (CanWorkSunday) numberOfDaysWorked++;
return numberOfDaysWorked;
}
}
}
There’s a more elegant approach that is utilized through the standard .net framework that we too can mimic.
This scenario is a perfect candidate for using a flagged enum and the power of bit operations.
[Flags]
public enum Days
{
None = 0,
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64,
MondayToFriday = Monday | Tuesday | Wednesday | Thursday | Friday,
Weekend = Saturday | Sunday,
All = MondayToFriday | Weekend,
}
public class WorkDays
{
public Days DaysWorked { get; set; }
/// <summary>
/// Get the Number of Days worked.
/// NOTE: Uses bitwise and operation.
/// </summary>
public int NumberOfDaysWorked
{
get
{
Days days = DaysWorked;
int numberOfDaysWorked = 0;
for (; days != 0; numberOfDaysWorked++)
{
days &= days - 1; // clear the least significant bit set
}
return numberOfDaysWorked;
}
}
}
By defining an enum with each successive value a power of 2 greater (i.e. binary) you can then use the bitwise or (|) and bitwise (&) operations to group and determine which values are selected.
[TestClass]
public class ExampleTestClass
{
[TestMethod]
public void Test()
{
var mondayToFriday = new WorkDays { DaysWorked = Days.MondayToFriday };
Assert.IsFalse(Days.Sunday.In(mondayToFriday.DaysWorked));
Assert.IsFalse(Days.Saturday.In(mondayToFriday.DaysWorked));
Assert.IsTrue(Days.Wednesday.In(mondayToFriday.DaysWorked));
Assert.AreEqual(5, mondayToFriday.NumberOfDaysWorked);
var monday = new WorkDays { DaysWorked = Days.Monday };
Assert.IsFalse(Days.Sunday.In(monday.DaysWorked));
Assert.IsTrue(Days.Monday.In(monday.DaysWorked));
Assert.AreEqual(1, monday.NumberOfDaysWorked);
var mondayAndWednesday = new WorkDays { DaysWorked = Days.Monday | Days.Wednesday };
Assert.IsFalse(Days.Tuesday.In(mondayAndWednesday.DaysWorked));
Assert.IsTrue(Days.Monday.In(mondayAndWednesday.DaysWorked));
Assert.IsTrue(Days.Wednesday.In(mondayAndWednesday.DaysWorked));
Assert.AreEqual(2, mondayToFriday.NumberOfDaysWorked);
var weekend = new WorkDays { DaysWorked = Days.Weekend};
Assert.IsTrue(Days.Saturday.In(weekend.DaysWorked));
Assert.IsTrue(Days.Sunday.In(weekend.DaysWorked));
Assert.IsFalse(Days.MondayToFriday.In(weekend.DaysWorked));
Assert.AreEqual(2, weekend.NumberOfDaysWorked);
}
}
public static class Extensions
{
/// <summary>
/// Is the specified enumeration value within the range of Enum values?
/// </summary>
/// <param name="value">the enum value to look for in the range</param>
/// <param name="range">the range of enum values (eg. bitwise OR'd values)</param>
/// <returns></returns>
public static bool In(this Enum value, Enum range)
{
var valueNumber = (int) Enum.Parse(value.GetType(), value.ToString());
var rangeNumber = (int) Enum.Parse(range.GetType(), range.ToString());
return (valueNumber & rangeNumber) == valueNumber;
}
}
For further information check out the following resources:
If you’re gotten a bit rusty on your bitwise operations:
http://en.wikipedia.org/wiki/Bitwise_operation
Flagged Enum:
http://weblogs.asp.net/wim/archive/2004/04/07/109095.aspx
