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
精彩评论