Royi Namir
Royi Namir

Reputation: 148744

Generic Constraint for allowing bitwise operations?

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 ?

Upvotes: 6

Views: 3837

Answers (3)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112762

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.

Upvotes: 5

John
John

Reputation: 3702

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

Upvotes: 2

user743382
user743382

Reputation:

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.

Upvotes: 3

Related Questions