xnp
xnp

Reputation: 51

Howto get flags from enum value?

Trying to get array of all possible flags from enum value, say 3 to array of {1, 2}.

I have an extension

internal static MyEnum[] GetFlags(this MyEnum modKey)
{            
    string[] splitStr = modKey.ToString().Split(new string[1] { ", " }, StringSplitOptions.RemoveEmptyEntries);

    MyEnum[] flags = new MyEnum[splitStr.Length];
    for (int i = 0; i < splitStr.Length; i++)
    {
        flags[i] = (MyEnum)Enum.Parse(typeof(MyEnum), splitStr[i]);
    }
    return flags;
}

...but it seems a bit wasteful for the purpose. Could this be done more effectively?

Upvotes: 0

Views: 3339

Answers (3)

Gert Arnold
Gert Arnold

Reputation: 109080

Both answers don't do what (I think) is asked: get the elementary values from an enum value, not any composed values. One example where this may be useful is when one enum value must be used in a Contains statement in LINQ to a SQL backend that doesn't support HasFlag.

For this purpose I first created a method that returns elementary flags from an enum type:

public static class EnumUtil
{
    public static IEnumerable<TEnum> GetFlags<TEnum>()
        where TEnum : Enum
    {
        return Enum.GetValues(typeof(TEnum))
            .Cast<TEnum>()
            .Where(v =>
            {
                var x = Convert.ToInt64(v); // because enums can be Int64
                return x != 0 && (x & (x - 1)) == 0;
                // Checks whether x is a power of 2
                // Example: when x = 16, the binary values are:
                // x:         10000
                // x-1:       01111
                // x & (x-1): 00000
            });
    }
}

And then a method that returns elementary flags from an enum value:

    public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum enumValue)
        where TEnum : Enum
    {
        return GetFlags<TEnum>()
            .Where(ev => enumValue.HasFlag(ev));
    }

Usage

Taking this enum type:

[Flags]
public enum WeekDay
{
    Monday = 1 << 0,
    Tuesday = 1 << 1,
    Wednesday = 1 << 2,
    Thursday = 1 << 3,
    Friday = 1 << 4,
    Saturday = 1 << 5,
    Sunday = 1 << 6,

    BusinessDay = Monday | Tuesday | Wednesday | Thursday | Friday,
    WeekendDay = Saturday | Sunday,
    All = BusinessDay | WeekendDay
}

The statements (in Linqpad)...

string.Join(",", EnumUtil.GetFlags<WeekDay>()).Dump();

var t = WeekDay.Thursday | WeekDay.WeekendDay;
string.Join(",", t.GetFlags()).Dump();

t = WeekDay.All;
string.Join(",", t.GetFlags()).Dump();

...return this:

Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
Thursday,Saturday,Sunday
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday

Basic idea taken from this answer to my question on Code Review.

Upvotes: 3

Gian Paolo
Gian Paolo

Reputation: 4249

assuming your MyEnum has a Flags Attribute, to test if a flag is set the (standard?) way is to perform a binary & between your value and the flag you want to test: so something like this should work:

internal static MyEnum[] GetFlags(this MyEnum modKey)
{
    List<MyEnum> flags = new List<MyEnum>();
    foreach (var flag in Enum.GetValues(typeof(MyEnum)))
    {
        if (modKey & flag == flag)
            flags.Add((MyEnum)flag);
    }
    return flags.ToArray(); 
}

if you use .Net 4 or later, you can use HasFlag

        if (modKey.HasFlag((MyEnum)flag))
            ...

Upvotes: 3

Jakub Lortz
Jakub Lortz

Reputation: 14896

You can simply filter all possible values of the MyEnum to the ones in modKey:

internal static MyEnum[] GetFlags(this MyEnum modKey)
{
    return Enum.GetValues(typeof(MyEnum))
        .Cast<MyEnum>()
        .Where(v => modKey.HasFlag(v))
        .ToArray();
}

Edit

Based on the comment below, in case of combinations specified, the method should only return the combinations, not all flags set.

The solution is to loop through all flags set in the enum starting from the highest one. In each iteration, we have to add a flag to the result, and remove it from the iterated enum until it's empty:

internal static MyEnum[] GetFlags(this MyEnum modKey)
{
    List<MyEnum> result = new List<MyEnum>();

    while (modKey != 0)
    {
        var highestFlag = Enum.GetValues(typeof(MyEnum))
            .Cast<MyEnum>()
            .OrderByDescending(v => v)
            .FirstOrDefault(v => modKey.HasFlag(v));

        result.Add(highestFlag);
        modKey ^= highestFlag;
    }

    return result.ToArray();
}

Upvotes: 6

Related Questions