eduncan911
eduncan911

Reputation: 17604

Compare two enums w/bitwise for a single True result?

How do you compare enums that have multiple bits set? I must be missing something simple.

I have a target persisted value of one enum, and I have the user's settings of that same enum. I need to compare the two to see if there is a single match of one or more enum bits set.

Bonus: I would love to use bitwise operators here to shortcut the linq query (cause I replicate this 5 or 6 times throughout different properties). I know it's not easily readable, but it would really help performance in what I am doing.

public enum Targets
{
  NotSet = 0,

  Anonymous = 1,
  Everyone = 2
  Adult = 4,
  Child = 8,

  LikesFishing = 16
}

I have users with multiple targets set:

var loggedInUser = new User()
{
  Username = "eduncan911",
  Targets = Targets.Everyone | Targets.Adult | Targets.LikesFishing
};    

I have articles set with multiple different targets:

var article1 = new Article()
{
  Title = "Announcement for Parents and Children",
  Targets = Targets.Adult | Targets.Child
};

var article2 = new Article()
{
  Title = "What fishing boat do you own?",
  Targets = Targets.LikesFishing | Targets.Adult
};

var article3 = new Article()
{
  Title = "Be nice to your parents!",
  Targets = Targets.Child
};

How would I query for Articles that has 1 Target bit set that matches at least 1 Targets of the specified user above (1 or more)? I should get back the first two articles because they match Targets.Adult - but the loggedInUser.Targets does not match any bit in the 3rd set of targets.

I know I can query articles for a specific Enum type, like this:

var articles =
  db.Articles.Where(x => x.Targets.HasFlag(Targets.LikesFishing);

But, I don't have a single Target - I have multiple bits set. Therefore, passing in just "loggedInUser.Targets" would never match any as the stored value is just an int.

At first, I was querying for enums like this:

// returns a collection of enums the user has set
// in their profile.
var loggedInUserEnums =
  Enum.GetValues(typeof(Targets))
    .Cast<Targets>()
    .Where(x => loggedInUser.Targets.HasFlag(x));

But when comparing the collection to the other collection of what articles have set, I was always getting back true for every article. I think I was going off to la-la land.

Is there a bitwise operation I can pass into the linq expression db.Articles.Where(...) to compare the two?

Just a guess, but I notice when I query for article targets that I have NotSet returning true as well - no matter of I ~Targets.NotSet or not. Odd.

Upvotes: 2

Views: 5272

Answers (4)

QYY
QYY

Reputation: 182

I'll use int instead of enum, something like:

    class Program {
    public const int NotSet = 1;
    public const int Anonymous = 1 << 2;
    public const int Everyone = 1 << 3;
    public const int Adult = 1 << 4;
    public const int Child = 1 << 5;
    public const int LikesFishing = 1 << 6;

    public static bool HasFlag(Article article, int flag) {
        return (article.Targets & flag) != 0;
    }

    public static bool HasFlags(Article article, params int[] flags) {
        foreach (int flag in flags) {
            if ((article.Targets & flag) == 0) return false;
        }
        return true;
    }

    static void Main(string[] args) {
        var article1 = new Article() {
            Title = "Announcement for Parents and Children",
            Targets = Adult | Child
        };

        var article2 = new Article() {
            Title = "What fishing boat do you own?",
            Targets = LikesFishing | Adult
        };

        var article3 = new Article() {
            Title = "Be nice to your parents!",
            Targets = Child
        };

        List<Article> db = new List<Article>() { article1, article2, article3 };

        var articles =
            db.Where(x => HasFlag(x, LikesFishing));

        foreach (Article article in articles) {
            Console.WriteLine(article.Title);
        }
    }
}

class Article {
    public string Title { get; set; }
    public int Targets { get; set; }
}

Upvotes: -1

marr75
marr75

Reputation: 5715

I think you meant to adorn your Enum with a flags decorator.

Additionally, depending on the linq provider you're using, this functionality may or may not be implemented. The provider needs to be able to break down the expression to produce an appropriate where clause and some providers can't handle what you're asking for. This leaves you with a couple of options.

You can write methods that will generate an expression your provider can handle. This is probably somewhere along the lines of .Where(x => x.Targets == 1 || x.Targets = 3 or x.Targets = 5...etc. Your code won't look like this, mind you, because you'll be dynamically generating the expression (this is a more advanced topic, see this article).

You can, more simply, move the targets enum to its own object with an identifier and a description. You can then join these with a many to many relationship and query on this.

Upvotes: 0

poindexter12
poindexter12

Reputation: 1783

First off you will want to make sure you attribute that enum with flags:

[Flags]
public enum Targets
{
    NotSet = 0,

    Anonymous = 1,
    Everyone = 2
    Adult = 4,
    Child = 8,

    LikesFishing = 16
}

Seconds, your LINQ would look like this:

var articlesThatLikeFishing = db.Articles.Where(x => (x.Targets & Targets.LikesFishing) == Targets.LikesFishing)

Upvotes: 0

Charles Bretana
Charles Bretana

Reputation: 146459

if you want to see the articles where the Target is either LikesFishing or Adult,

try this:

 var target = Targets.LikesFishing | Targets.Adult;
 var articles =   db.Articles.Where(x => (int)(x.Targets & target) > 0 ); 

and oh, yes, add the [FlagsAttribute] to the enum:

[Flags]
public enum Targets 
{   NotSet = 0,  Anonymous = 1, Everyone = 2,
    Adult = 4,   Child = 8,    LikesFishing = 16 }

Upvotes: 6

Related Questions