Most efficient way to parse a flagged enum to a list

Viewed 25750

I have a flagged enum and need to retrieve the names of all values set on it.

I am currently taking advantage of the enum's ToString() method which returns the elements comma-separated.

public void SetRoles(Enums.Roles role)
{
    IList<Entities.Role> roleList = role.ToString("G").Split(',')
        .Select(r => new Entities.Role(r.Trim()))
        .ToList();
    ...
}

I'm sure there must be a better way than this.

7 Answers
List<string> GetRoleNames(Roles roles) =>
    Enum.GetValues(typeof(Roles))
        .Cast<Roles>()
        .Where(role => roles.HasFlag(role))
        .Select(role => role.ToString())
        .ToList();

void TestRoleSelection()
{
    var selectedRoles = (Roles)6;
    var roleNames = GetRoleNames(selectedRoles);
    Console.WriteLine(string.Join(",", roleNames));
    // Output: Admin,User
}

[Flags]
enum Roles
{
    SuperAdmin = 1,
    Admin = 2,
    User = 4,
    Anonymous = 8
}

Similar answer to Mick's but puts the operations into extensions and fixes/cleans up the extra space character (from the split).

Also as a bonus if the enum has a _ in it, the code changes it to a space.

public static class EnumExtensions
{  
    // Take anded flag enum and extract the cleaned string values.
    public static List<string> ToComparableStrings(this Enum eNum)
        =>  eNum.ToString()
                .Split(',')
                .Select(str => str.ToCleanString())
                .ToList();

    // Take an individual enum and report the textual value.
    public static string ToComparableString(this Enum eNum)
        => eNum.ToString()
               .ToCleanString();

    // Remove any spaces due to split and if `_` found change it to space.
    public static string ToCleanString(this string str)
        => str.Replace(" ", string.Empty)
              .Replace('_', ' ');
}

Usage

var single   = PivotFilter.Dollars_Only;
var multiple = PivotFilter.Dollars_Only | PivotFilter.Non_Productive;

                                // These calls return:
single.ToComparableString()     // "Dollars Only"
multiple.ToComparableString()   // "Non Productive,Dollars Only"
multiple.ToComparableStrings()  // List<string>() { "Non Productive", "Dollars Only" }

Enum for Usage

[Flags]
// Define other methods, classes and namespaces here
public enum PivotFilter
{
    Agency = 1,
    Regular = 2,
    Overtime = 4,
    Non_Productive = 8,
    Dollars_Only = 16,
    Ignore = 32
}

Turning a flagged enum to a list might not be as straight forward as it looks. Take the following enum for example:

[Flags]
enum MenuItems
{
    None = 0,
    Pizza = 1,
    Fries = 2,
    Pancakes = 4,
    Meatballs = 8,
    Pasta = 16,
    StuffWithP = Pizza | Pancakes | Pasta,
    All = Pizza | Fries | Pancakes | Meatballs | Pasta | StuffWithP
};

If we have the value StuffWithP, what do we want in the list? StuffWithP or Pizza, Pancakes, Pasta? I had a use case in witch I needed to "deconstruct" the enum value to the invidual flags and put those in a list. I came up with the following:

public static string[] DeconstructFlags(Enum items)
{
    if (items.GetType().GetCustomAttribute<FlagsAttribute>() == null)
    {
        throw new ArgumentException("Enum has no [Flags] attribute.", nameof(items));
    }

    // no value, no list
    var itemsValue = (int)(object)items;
    if (itemsValue == 0) return Array.Empty<string>();

    var result = new List<string>();

    foreach (var item in Enum.GetValues(items.GetType()))
    {
        if(item == null) continue;

        var value = (int)item;

        // skip combined flags
        if (!BitOperations.IsPow2(value))
        {
            continue;
        }

        if (items.HasFlag((Enum)item))
        {
            result.Add(item.ToString() ?? "");
        }
    }

    return result.ToArray();
}

I don't know if it is the most efficient, but it skips those combined flags nicely. Wrote some more on my blog: Deconstructing a [Flags] enum.

Related