开发者

Most efficient way to parse a flagged enum to a list

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.


Try this:

public void SetRoles(Enums.Roles role)
{
  List<string> result = new List<string>();
  foreach(Roles r in Enum.GetValues(typeof(Roles)))
  {
    if ((role & r) != 0) result.Add(r.ToString());
  }
}


If you genuinely just want the strings, can't get much simpler than:

string[] flags = role.ToString().Split(',');

This is simpler than using LINQ and is still just a single line of code. Or if you want a list instead of an array as in the sample in the question you can convert the array into a list:

List<string> flags = new List<string>(role.ToString().Split(','));

In my case I needed a generic solution and came up with this:

value.ToString().Split(',').Select(flag => (T)Enum.Parse(typeof(T), flag)).ToList();


Enum.Parse will handle the concatenated values outputted by ToString just fine. Proof using the Immediate window:

? System.Enum.Parse(typeof(System.AttributeTargets), "Class, Enum")
Class | Enum

(the second line is the output, which is different in the debugger/immediate window from the generic Enum.ToString() output).


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
}


Why do you need a list? Everything is already stored in the flags:

[Flags]
enum Roles
{
    Read = 0x1,
    Write = 0x2,
    Delete = 0x4,
}

Then assign roles:

var roles = Roles.Read | Roles.Write;

And whenever you need to check if a given role has been you don't need to look in a list, but simply look in the roles enumeration:

if ((roles & Roles.Read) == Roles.Read)
{
    // The user has read permission
}
if ((roles & Roles.Write) == Roles.Write)
{
    // The user has write permission
}


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.


F# version

module Enum =

    let inline flagsToList< ^e when ^e: equality and ^e: (static member (&&&): ^e * ^e -> ^e)> value =
        Enum.GetValues(typeof<^e>)
        |> Seq.cast<^e>
        |> Seq.where (fun case -> case &&& value = case)
        |> Seq.toList
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜