12

I'm trying to make a helper method for listing the names of all bits set in an Enum value (for logging purposes). I want have a method that would return the list of all the Enum values set in some variables. In my example

[Flag]
Enum HWResponse
{
   None = 0x0,
   Ready = 0x1,
   Working = 0x2,
   Error = 0x80,
}

I feed it 0x81, and it should provide me with a IEnumerable<HWResponse> containing {Ready, Error}.

As I didn't find a simpler way, I tried to write the code below, but I can't make it compile.

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{
  if (typeof(T).IsSubclassOf(typeof(Enum)) == false)
    throw new ArgumentException();

  List<T> toreturn = new List<T>(100);

  foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>())
  {
    Enum bit = ((Enum) curValueBit);  // Here is the error

    if (mask.HasFlag(bit))
      toreturn.Add(curValueBit);
  }

  return toreturn;
}

On this version of the code, the compiler complains that it can't cast T to Enum.

What did I do wrong? Is there a better (simpler) way to do this? How could I make the cast?

Also, I tried to write the method as

public static IEnumerable<T> MaskToList<T>(Enum mask) where T:Enum

but Enum is of a special type that forbids the 'where' syntax (Using C# 4.0)

Oded
  • 489,969
  • 99
  • 883
  • 1,009
PPC
  • 1,734
  • 1
  • 21
  • 41

5 Answers5

27

Here's a simple way to write it using LINQ:

public static IEnumerable<T> MaskToList<T>(Enum mask)
{
    if (typeof(T).IsSubclassOf(typeof(Enum)) == false)
        throw new ArgumentException();

    return Enum.GetValues(typeof(T))
                         .Cast<Enum>()
                         .Where(m => mask.HasFlag(m))
                         .Cast<T>();
}
Peter Hansen
  • 8,807
  • 1
  • 36
  • 44
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • 2
    Great solution: simple, readable and short. One drawback though: HasFlag(m) also lists the state 'None' (0x0); but it's easy to overcome by an old style binary & – PPC Apr 12 '12 at 18:03
  • 1
    Also HasFlag() seems to have big performance issues: see another thread on http://stackoverflow.com/a/7164314/1121983 – PPC Apr 12 '12 at 18:08
  • Does this actually work? When I try to cast `var value = (T)Enum.GetValues(typeof(T)).Cast().FirstOrDefault(m => mask.HasFlag(m))` compiler complains. – JobaDiniz Sep 19 '17 at 12:12
3

If your desired end result is a string list of names, just call mask.ToString().

What would you do if the enum were defined like this:

[Flags]
enum State
{
    Ready = 1,
    Waiting = 2,
    ReadyAndWaiting = 3
}

As to resolving the compiler error, this should do it:

Enum bit = (Enum)(object)curValueBit;

Jon Skeet has a project called unconstrained melody that allows you to add the enum constraint, after compilation, by rewriting the IL. This works because the CLR supports such a constraint, even though C# does not.

Another thought: It will be more efficient to cast the return value of GetValues directly to T[]:

foreach(T curValueBit in (T[])Enum.GetValues(typeof (T)))
phoog
  • 42,068
  • 6
  • 79
  • 117
  • mask.ToString() is indeed what I finally wanted (though I'd rather have a more flexible solution). It looks like a very similar code is implemented in the framework to allow such a result. I'd like to see it :) – PPC Apr 12 '12 at 17:59
  • Also, I'm not fan of the 'ReadyAndWaiting' solution: my real enum has 14 flags, and I'm not going to implement all possible stages with such long names :) – PPC Apr 12 '12 at 18:00
  • Last but not least, could you explain a little more the cast to T[]? Performance issues? – PPC Apr 12 '12 at 18:01
  • 1
    @PPC I am not particularly a fan of `ReadyAndWaiting` either, but if you're going to be dealing with enums you didn't define, you might have to deal with the possibility. Otherwise, it's moot. As for performance, it's not as bad as I thought: the expression `(T[])Enum.GetValues(typeof (T)))` gives you a `T[]`; array foreach loops are converted by the C# compiler into for loops, which are more efficient. If you call `Cast`, you end up with a reference to the array as an `IEnumerable`, so you're using the `IEnumerator` object, which is slightly less efficient. – phoog Apr 12 '12 at 18:12
  • 1
    @PPC to see the framework's implementation of `ToString` for flags enums, download [ILSpy](http://wiki.sharpdevelop.net/ilspy.ashx) and decompile `System.Enum.InternalFlagsFormat`. – phoog Apr 12 '12 at 18:16
2

Building on Gabe's answer I came up with this :

public static class EnumHelper<T>
    where T : struct
{
    // ReSharper disable StaticFieldInGenericType
    private static readonly Enum[] Values;
    // ReSharper restore StaticFieldInGenericType
    private static readonly T DefaultValue;

    static EnumHelper()
    {
        var type = typeof(T);
        if (type.IsSubclassOf(typeof(Enum)) == false)
        {
            throw new ArgumentException();
        }
        Values = Enum.GetValues(type).Cast<Enum>().ToArray();
        DefaultValue = default(T);
    }

    public static T[] MaskToList(Enum mask, bool ignoreDefault = true)
    {
        var q = Values.Where(mask.HasFlag);
        if (ignoreDefault)
        {
            q = q.Where(v => !v.Equals(DefaultValue));
        }
        return q.Cast<T>().ToArray();
    }
}

I organized things a bit differently, namely I put the type check (i.e.: the verification that T is really an enumeration) and the obtaining of the enum values in the static constructor so this is done only once (this would be a performance improvement).

Another thing, I added an optional parameter so you can ignore the typical "zero" / "None" / "NotApplicable" / "Undefined" / etc value of the enumeration.

Community
  • 1
  • 1
Andrei Rînea
  • 20,288
  • 17
  • 117
  • 166
1

What if just do something like this:

public static IEnumerable<T> MaskToList<T>(Enum mask)
{
 if (typeof(T).IsSubclassOf(typeof(Enum)) == false)
    throw new ArgumentException();

  List<T> toreturn = new List<T>(100);

  foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>())
  {
    Enum bit = (curValueBit as Enum);  // The only difference is actually here, 
                                       //  use "as", instead of (Enum) cast

    if (mask.HasFlag(bit))
      toreturn.Add(curValueBit);
  }

  return toreturn;
}

As the as has not compile time check. Compiler here just "believes" you, hoping that you know what you're doing, so the compile time error not raised.

Tigran
  • 61,654
  • 8
  • 86
  • 123
  • Why not `foreach(Enum curValueBit in (T[])Enum.GetValues(typeof (T)))`? Also, for usefulness as an extension it may be better to have it take T mask instead of Enum mask – Random832 Apr 11 '12 at 19:10
  • The foreach statement works, though the body of the loop needs modification and it may have different performance characteristics. I suspect the cast to T[] instead of .Cast is always better. I'd be worried about phoog's case, with defined values that have more than one flag. What does HasFlag do in this case? – Random832 Apr 11 '12 at 19:18
  • @Random832: exactly, you need cast *by the way*. I just put hit own code with one line changed, that is.. – Tigran Apr 11 '12 at 19:20
  • @Random(1st question): I tried first with Ienumerable MaskToList(T mask), but left it away in my fight with the compiler. It doesn't allow me to use mask.HasFlag() and I don't find the right cast (looking for something like "if ((mask&(T)bit) != 0)") – PPC Apr 12 '12 at 17:57
  • 1
    @Random832: infact I didn't use `T`, but used `Enum` *and* used `as` operator to make it compile and work. – Tigran Apr 12 '12 at 18:00
1

I spent some time on searching how to convert Flags enum value to List. I have found pretty simple solution, maybe it will help someone.

  [Flags]
  public enum Tag
  {
    None = 0,
    Stablecoin = 1,
    NativeTokens = 2,
    Dex = 4
}
var values = Tag.Stablecoin | Tag.Dex;
var str = values.ToString(); //"Stablecoin, Dex"
var list = uniqueNftTagsV2.Split(", "); //{"Stablecoin","Dex"}
Piotr P
  • 294
  • 1
  • 4
  • 12