C# Multi-Valued Enums [Flags]
February 13th 2008
So here’s my problem (in case you run into it);
I have few enums in my project. Those include DayOfWeek, MonthOfYear, RecurrenceDay etc. What’s common between those enums is I want to be able to select one, multiple, all or none. So what are some guidelines to do this?
Hmm… ok let’s dig in.
In my case the rules are:
1. Have a None, and All values deified in my enum.
2. Decorate the enum with “[Flags”] to enable bitwise operations.
[Flags]
enum RecurrenceDay
{
None = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64,
All = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
}
Notice that enum have been set to powers of 2 to avoid overlapping of combinations. What do I mean? Since the above is a multiple selection enum you can end up with something like this.
RecurrenceDay days = RecurrenceDay.Monday | RecurrenceDay.Saturday | RecurrenceDay.Friday;
Which in this case has an int value of 49. Actually the combinations’ values look something like this.
to
(Now this is a simplistic explanation. Want to get deeper? Read up on the FlagsAttribute and bitwise operations. Otherwise just follow what’s done here and that should satisfy 80% of scenarios)
Ok so if I get 49 above, would that help me? most probably not. What I really need is a method that could take in the enumerated value and return an array or collection of the selected values which is exactly what the following method does.
public static Collection<RecurrenceDay> GetSelectedDays(RecurrenceDay days) { Collection<RecurrenceDay> selectedRecurrenceDays = new Collection<RecurrenceDay>(); if ((RecurrenceDay.Friday & days) != 0) { selectedRecurrenceDays.Add(RecurrenceDay.Friday); } /// /// /// if ((RecurrenceDay.Thursday) != 0) { selectedRecurrenceDays.Add(RecurrenceDay.Thursday); } return selectedRecurrenceDays; }
Now while the above method works I have 2 problems with it. One can be solved easily and that is the enumerator value ‘All’ will always be returned. Secondly the method is just not generic enough. Every time I have a different multi-value enum I will end up with a new method, plus too many if statements is not my favourite coding habit. So I came up with the following generic method that I think will be helpful if you ever run into this scenario.
public static Collection<T> GetSelectedEnumValues<T>(T intputValue, T? allValue) where T : struct { Type enumType = typeof(T); if (!enumType.IsEnum) { throw new ArgumentException(typeof(T).ToString() + ” is not an Enum.”, “T”, null); } //Get all the values of the enumerator Array enumValues = Enum.GetValues(enumType); //Instantiate collection of T Collection<T> output = new Collection<T>(); //Iterate all values of T //Filter out all methods that are selected using ‘&’ //Also filter out the ‘All’ enumerator if it’s not null foreach (T enumValue in enumValues) { if ((Convert.ToInt32(enumValue) & Convert.ToInt32(intputValue)) != 0) { if (allValue.HasValue) { if (!enumValue.Equals(allValue.Value)) { output.Add(enumValue); } } else { output.Add(enumValue); } } } return output; }
So now I can use the method above in something like the code below, allowing me to iterate through the selected enums.
static void Main(string[] args) { RecurrenceDay days = RecurrenceDay.Monday | RecurrenceDay.Saturday | RecurrenceDay.Friday; foreach (RecurrenceDay day in GetSelectedEnumValues(days, RecurrenceDay.All)) { Console.WriteLine(day.ToString()); } Console.ReadKey(); }
Which gives me the result of
Now sometimes you don’t have an ‘All’ enum. In that case you just pass null to the method for the ‘allValue’ parameter.
Happy Enumerating!
P.S. Just wanted to say thank you to Ducas Francis & Tim Sadler who when I went to with this problem they assisted by the very enlightening answer “GOOGLE IT!”. Thanks guys. It really did help
Also thanks to Paul Stovell for reviewing the code and acting as my “Human Unit Test”
UPDATE:
So After talking to Marc Riday he pointed out some nice tips. Using Marc’s tips and some refactoring the final result is.
/// <summary> /// Constructs a collection of selected enums out of /// a multi-value enum (one using the FlagsAttrbute). /// <remarks> /// The method will do the following: /// 1. Check if T is an enum and throw an argument /// exception if it is not. /// 2. Iterate through the list of possible values /// of the enum and check it against the passed /// in value. /// 3. If a ‘None’ value exists and no matching /// enum is found then a check will be done /// to return the ‘None’ value. /// </remarks> /// </summary> /// <example> /// A typical enum used with this method. /// [Flags] /// enum RecurrenceDay /// { /// None = 0, /// Monday = 1, /// Tuesday = 2, /// Wednesday = 4, /// Thursday = 8, /// Friday = 16, /// Saturday = 32, /// Sunday = 64, /// All = Monday | Tuesday | Wednesday | Thursday | Friday | /// Saturday | Sunday /// } /// </example> /// <typeparam name=”T”>Generic type of the enum.</typeparam> /// <param name=”enumInputValue”>The selected enums value.</param> /// <returns>A collection of selected enums.</returns> public static Collection<T> GetSelectedEnumValues2<T>(T enumInputValue) where T : struct { Type enumType = typeof(T); if (!enumType.IsEnum) { throw new ArgumentException(enumType.ToString() + ” is not an Enum.”, “T”, null); } Array enumValues = Enum.GetValues(enumType); long inputValueLong = Convert.ToInt64(enumInputValue); Collection<T> enumOutputCollection = new Collection<T>(); foreach (T enumValue in enumValues) { long enumValueLong = Convert.ToInt64(enumValue); if ((enumValueLong & inputValueLong) == enumValueLong && enumValueLong != 0) { enumOutputCollection.Add(enumValue); } } if (enumOutputCollection.Count == 0 && enumValues.GetLength(0) > 1) { T enumNoneValue = (T)enumValues.GetValue(0); if (Convert.ToInt64(enumNoneValue) == 0) { enumOutputCollection.Add(enumNoneValue); } } return enumOutputCollection; }
