3

I have a list of records that contains a flags enum value. I am trying to filter on one or more value based on the flags supplied. The description of the HasFlag method is as follows:

true if the bit field or bit fields that are set in flag are also set in the current instance; otherwise, false.

I would expect given an enum:

[Flags]
public enum MyFlaggedEnum
{
    Unknown = 0,
    First = 1,
    Second = 2,
    Third = 4,
    Fourth = 8,
    Fifth = 16,
    Sixth = 32
}

And a record set containing a mixture of these values, assume that there are at least one record for each of the flags:

When I filter on one value:

var myFilteredList = MyMainList.Where(w => w.flagValue.HasFlag(MyFlaggedEnum.Third));
// myFilteredList returns the correct subset of records

When I filter on multiple values:

var myFilteredListMultiple = MyMainList.Where(w => w.flagValue.HasFlag(MyFlaggedEnum.First | MyFlaggedEnum.Fourth));
// myFilteredList returns an empty set

Am I misusing the HasFlag method; if so, how?

I am looking for a way to quickly filter the results so they can be displayed to the user in a datagridview based on filters they supply at runtime and can be changed at any time. I could loop through them or union multiple sets based on the flag filters requested by the user, but, was looking for a clean way.

EDIT: Whoever closed this question referring to this: How to check if any flags of a flag combination are set? does not understand the question. No, I am not adding First | Second values to my enum!!

JimmyV
  • 493
  • 3
  • 12
  • Yeah, you should be doing `w => w.flagValue.HasFlag(MyFlaggedEnum.First) || w.flagValue.HasFlag(MyFlaggedEnum.Fourth)` if you want to check for one of those two flags, or use `&&` if you only want ones with both flags. – juharr Jun 30 '21 at 14:09
  • 1
    If you OR some values together and pass them to `HasFlag` it will return true only if ALL the bits corresponding to the passed value are set. It's an AND rather than an OR. – Matthew Watson Jun 30 '21 at 14:09
  • @juharr I was hoping to avoid having to build a query that way since I will not know what flags will be requested by the user. I went with the HasFlag because it mentioned "bit fields". – JimmyV Jun 30 '21 at 14:11
  • In that case go with regular bit logic like the question Sinatr posted – juharr Jun 30 '21 at 14:12
  • 1
    @juharr why did you close this as a duplicate? That answer did not help me at all since my question involved working with lambda. I read it before asking the question and Matthew's answer below helped immediately. I hope that someone else with my specific issue can still find this question and get the correct answer. – JimmyV Jun 30 '21 at 19:56
  • @JimmyV The important bit in the answer to that question is the `(letter & Letters.AB) != 0` which is equivalent to the `(w.FlagValue & mask) != 0` in Matthew's answer. The fact that you use it in a lambda is not the important bit. – juharr Jun 30 '21 at 20:37
  • @juharr that's your opinion on that. I read the "duplicate" article in addition to others and found them to not be helpful in this case, thus, posting the question here. Closing this as a duplicate assumes that someone else picks up the same "important bit" that you happen to and doesn't take into account the different way people consume and implement information. Closing this closes discussion and could force them to comment on a years old question. – JimmyV Jun 30 '21 at 21:10
  • @JimmyV Clearly closing a question doesn't close commenting as that's what we are doing now. – juharr Jun 30 '21 at 21:32

2 Answers2

3

I suggest that you eschew HasFlag and just do it directly using bit operations:

var myFilteredListMultiple = MyMainList.Where(w => (w.FlagValue & (MyFlaggedEnum.First | MyFlaggedEnum.Fourth)) != 0);

Or if you have a set of bitflags passed in called, say, mask:

var myFilteredListMultiple = MyMainList.Where(w => (w.FlagValue & mask) != 0);
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
3

There's an ambiguity over whether HasFlag should mean "has all" vs "has any"; it is implemented as "has all". But it is also an inefficient (boxing) API, so honestly: it is probably better to simply not use it. For example, you could add your own extension methods:

public static bool HasAny(this MyFlaggedEnum value, MyFlaggedEnum any)
    => (value & any) != 0;
public static bool HasAll(this MyFlaggedEnum value, MyFlaggedEnum all)
    => (value & all) == all;

and use .HasAny(MyFlaggedEnum.First | MyFlaggedEnum.Fourth)

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900