Reputation: 148744
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 :
Question :
Which generic constraint should I apply in order for this to work ?
Upvotes: 6
Views: 3837
Reputation: 112762
There is no appropriate generic type constraint for this situation. You can apply bitwise operations to int
s, 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
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)));
}
Upvotes: 2
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