List all bit names from a flag Enum

2019-01-18 12:28发布

问题:

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)

回答1:

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


回答2:

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


回答3:

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.



回答4:

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.