6

I have an enum :

  public enum Flags {
        COMMAND_MSG = 1,
        COMMAND_FILE = 2,
        COMMAND_ACTION = 4,
      }

Now , suppose I set multiple values like :

Flags g = Flags.COMMAND_ACTION |Flags.COMMAND_MSG; 

So I have an int with value 5.

Now , from that 5 I want to see that it's a combination of : Flags.COMMAND_ACTION |Flags.COMMAND_MSG;

(Notice , I dont have [Flags] attribute because I'm using protobuff library , and the enum is auto generated.

What have I tried :

public string Show (Flags item)
{
  var s="";
  string.Join(",", Enum.GetValues(typeof(Flags))
        .Cast<Flags>() 
        .Select(f=>(f & item) >0 ?f.ToString() :"") //check if bit is set
        .Where(f=>!string.IsNullOrWhiteSpace(f))); //remove empty
  return s;
}

So Show(5); does display : COMMAND_MSG,COMMAND_ACTION

So where is the problem ?

I wanted to convert it to a generic extension method :

 public static string ToFlags<T>(this int val, T FlagType) where T : ??

    {
        return string.Join(",", Enum.GetValues(typeof(T))
                     .Cast<T>()
                     .Select(enumEntry => (enumEntry & val) > 0 ? enumEntry.ToString() : "")
                     .Where(f => !string.IsNullOrWhiteSpace(f)));
    }

But there's an error because :

enter image description here

Question :

Which generic constraint should I apply in order for this to work ?

Royi Namir
  • 144,742
  • 138
  • 468
  • 792

3 Answers3

5

There is no appropriate generic type constraint for this situation. You can apply bitwise operations to ints, so convert the enum value to int. T cannot be cast to int directly; however, you can make the detour over object.

public static string ToFlags<T>(this T val)
    where T : struct
{
    return string.Join(",", Enum.GetValues(typeof(T))
        .Cast<T>()
        .Select(enumEntry => ((int)(object)enumEntry & (int)(object)val) > 0
            ? enumEntry.ToString() : "")
        .Where(f => !string.IsNullOrWhiteSpace(f)));
}

Note: There is a similar situation with numbers. You can't say where T : number and apply numeric operators on values of type T. (See my update below)

Also you can make val a T and you don't have to pass the type of T as method argument. You are passing it as generic type argument already. This is sufficient. The compiler is also smart enough to infer T, so you don't have to specify it when calling the method.

// Test
Flags item = Flags.COMMAND_ACTION | Flags.COMMAND_MSG;
Console.WriteLine(item.ToFlags());

You can specify struct as generic type constraint, as struct stands for value types. It is not perfect, but better than no constraint at all.


Update

A long time has passed since this question has been asked. C# 11 introduced static virtual members in interfaces and the .NET 7 BCL the interfaces IBitwiseOperators<T, T, T> and IEqualityOperators<T, T, bool>. These interfaces (and many others) are implemented for numeric types but unfortunately not for enums.

See: [API Proposal]: Enum should implement IBitwiseOperators, IEqualityOperators #81664.

Since C# 7.3 there is an Enum generic constraint that can be specified in addition to struct.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 1
    The cast from a generic parameter type to `int` should be flagged by the compiler as an error. Does this really work for you? –  Dec 26 '14 at 12:56
  • I changed it. You can fool the compiler by first casting to `object`. – Olivier Jacot-Descombes Dec 26 '14 at 13:02
  • Note that this will fail for enumerations with an underlying type other than `int`: it will then cause an `InvalidCastException`. But if the enumeration is known in advance to have an underlying type of `int`, this is a good approach. –  Dec 26 '14 at 13:04
  • 1
    @RoyiNamir `.ToFlags(default(Flags))`, or get rid of your `FlagType` parameter and write `.ToFlags()`? –  Dec 26 '14 at 13:09
3

There is no constraint you can use to allow any particular operator. .NET just isn't designed like that.

However, you don't need that. Instead of checking (enumEntry & val) > 0, you can use Enum.HasFlag.

((Enum)enumEntry).HasFlag((Enum)Enum.ToObject(enumEntry.GetType(), val))

Yes, admittedly, this looks quite ugly.

  • That's fine, but it only allows you to check the flag. How does one go about setting it? If I have enumEntry1 and enumEntry2, I should be able to get enumEntry1 | enumEntry2, but there is no ".SetFlag()" method... – Sean Worle Dec 05 '19 at 00:00
2

This works as intended:

    public static string ToFlags<T>(this int val) where T : struct, IConvertible
    {
        return string.Join(",", Enum.GetValues(typeof(T))
            .Cast<T>()
            .Select(x => new KeyValuePair<T, int>(x, Convert.ToInt32(x)))
            .Select(kv => (kv.Value & val) > 0 ? kv.Key.ToString() : "")
            .Where(f => !string.IsNullOrWhiteSpace(f)));
    }
  1. I don't see the need for the extra parameter to the function.
  2. The closest restriction to Enum i can come up with is struct, IConvertible
John
  • 3,627
  • 1
  • 12
  • 13
  • 2
    C# 7.3 adds support for `where T : Enum` which is a slight improvement, but still doesn't allow the use of bitwise operations without an ugly `(Int64)(Object)` cast. – Dai Aug 08 '19 at 00:10