48

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.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
David Neale
  • 16,498
  • 6
  • 59
  • 85
  • 2
    Good to know that something like this exists - `the enum's ToString() method which returns the elements comma-separated`. You made my day :). +1. – RBT Dec 22 '16 at 07:43

8 Answers8

47

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());
  }
}
live2
  • 3,771
  • 2
  • 37
  • 46
Grzenio
  • 35,875
  • 47
  • 158
  • 240
  • 10
    Better replace `(role & r) != 0` with `(role & r) == r` or `role.HasFlag(r)` in .net4. And make sure you assign values that are a power of 2, otherwise it will fail. See http://stackoverflow.com/questions/8447/enum-flags-attribute – Jorrit Salverda Jan 08 '13 at 14:05
  • 22
    Here is one-line version of the same: `Enum.GetValues(typeof(Roles)).Cast().Where(r => (role & r) == r).Select(r => r.ToString())` – Ondra Jan 07 '14 at 19:19
  • 4
    Also, instead of if ((role & r) != 0) use role.HasFlag(r) – dlchambers Jul 08 '15 at 20:42
  • 1
    Dangerous for two reasons: 1) Imagine you have a value equal to 0 in the enum. With your code, this value will be always present in the list no matter what combination you have. 2) Now if you have a combination INSIDE your enum, the list will count the individual elements AND the corresponding combination. – IRONicMAN Jul 17 '18 at 17:30
33

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();

Mick Bruno
  • 1,373
  • 15
  • 13
  • Best answer. Thank you! – Ricsie Oct 24 '16 at 07:37
  • Except it doesn't even work properly -- it adds a space on all list elements except on first because there is a space after comma in the `ToString()` generated list you are splitting. – Igor Levicki Nov 22 '19 at 18:39
  • 1
    If you don't want any spaces, just add a line 'result = result.Replace(" ","")' And the question did not specify that there could not be spaces after the commas! This is a beginner-level fix to remove the spaces. Seems silly to have to include how to do this or to say that the solution does not work because of it. – Mick Bruno Sep 02 '20 at 16:24
  • 1
    I'm late to the party, but you can also split by ", " with the space inside, then space will be omitted in the results. – 0Pat Dec 15 '22 at 09:42
7

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).

Alex Paven
  • 5,539
  • 2
  • 21
  • 35
3
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
}
2

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
}
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
1

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
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 3
    That is how the application is using it. However, this is more of a persistance concern. I'd rather leave this method unaware of what values the enum might contain. – David Neale Sep 08 '10 at 14:10
  • 3
    If it is for persistence all you need to store is a single integer representing the flags because this integer allows you to later extract all the roles. No need to store lists. Otherwise the `[Flags]` is kind of unnecessary if you are going to keep lists of roles. You will also gain a few bytes of storage media :-) – Darin Dimitrov Sep 08 '10 at 14:11
  • 1
    However, sometimes it's important to keep the stored data in a somewhat human-readable format. In that case, Enum.Parse can handle concatenated enum values just fine, similarly to how ToString() outputs them. So `Enum.Parse(typeof(Roles), "Read, Write")` will result in the correct Read | Write value. Actually I'll write an answer with this info. – Alex Paven Sep 08 '10 at 14:18
  • 2
    @David, the whole point of [`Flags`](http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx) is that it allows you to store multiple values into a single integer. – Darin Dimitrov Sep 08 '10 at 14:18
  • @Darin. My authorisation strategy has somewhat changed and you're right, there isn't any need for this. However, could there not be a time when somebody might like to map a flagged enum value to a database record or similar? – David Neale Sep 08 '10 at 14:29
  • 2
    I agree with David. While the power of flag enums is to concatenate multiple settings into one value, sometimes being human-readable is better than being compact. What's the value of 1064? OK, 1024 & 32 & 8, but what do those values mean? Would you even have known to split up the flags, or would you have thought this was just some predefined single status code? You'd have to inspect the code, or similarly provide some documentation of the value which has to be found and read. Space is cheap; store it as "Read, Write, Delete" and someone dealing with raw data can understand it at a glance. – KeithS Sep 08 '10 at 22:01
  • Sometimes having an `IList` representation on hand will help with analysis. – Panzercrisis Oct 13 '16 at 14:48
0

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.

Kees C. Bakker
  • 32,294
  • 27
  • 115
  • 203
0

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
Andrii
  • 1,081
  • 1
  • 11
  • 24