Сравнить два перечисления с побитовым значением для одного истинного результата?

Как вы сравниваете перечисления, в которых установлено несколько битов? Я должен упустить что-то простое.

У меня есть целевое постоянное значение одного перечисления, и у меня есть пользовательские настройки того же перечисления. Мне нужно сравнить их, чтобы увидеть, есть ли совпадение одного или нескольких битов перечисления.

Бонус: я хотел бы использовать здесь побитовые операторы, чтобы сократить запрос linq (потому что я повторяю это 5 или 6 раз в разных свойствах). Я знаю, что это не легко читается, но это действительно поможет производительности в том, что я делаю.

public enum Targets
{
  NotSet = 0,

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

  LikesFishing = 16
}

У меня есть пользователи с несколькими установленными целями:

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

У меня есть статьи с несколькими разными целями:

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
};

Как мне запросить статьи, в которых установлен 1 целевой бит, соответствующий как минимум 1 цели указанного выше пользователя (1 или более)? Я должен вернуть первые две статьи, потому что они соответствуют Targets.Adult, но loggedInUser.Targets не соответствует ни одному биту в третьем наборе целей.

Я знаю, что могу запрашивать статьи для определенного типа Enum, например:

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

Но у меня нет одной цели - у меня установлено несколько битов. Следовательно, передача только «loggedInUser.Targets» никогда не будет соответствовать ни одному из них, поскольку сохраненное значение является просто целым числом.

Сначала я запрашивал перечисления следующим образом:

// 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));

Но при сравнении коллекции с другой коллекцией того, какие статьи установлены, я всегда возвращал истину для каждой статьи. Я думаю, что собирался отправиться в ла-ла-ленд.

Есть ли побитовая операция, которую я могу передать в выражение linq db.Articles.Where(...) для сравнения двух?

Просто предположение, но я замечаю, что когда я запрашиваю цели статьи, у меня NotSet также возвращает true - независимо от того, я ~Targets.NotSet или нет. Странный.


person eduncan911    schedule 15.12.2010    source источник
comment
Дополнительная пища для размышлений: нужно ли мне зацикливать loggedInUserEnums и запрашивать для каждого Enum? Это кажется мне дорогостоящим, когда я думаю, что для этого должен быть побитовый запрос.   -  person eduncan911    schedule 16.12.2010
comment
Можно ли предположить, что под Linq вы подразумеваете linq to sql или какой-либо другой поставщик запросов к базе данных linq?   -  person marr75    schedule 16.12.2010
comment
Я прошу прощения. Я имею в виду Lambda, а не linq.   -  person eduncan911    schedule 16.12.2010


Ответы (4)


если вы хотите увидеть статьи, в которых целью является LikesFishing или Adult,

попробуй это:

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

и да, добавьте [FlagsAttribute] в перечисление:

[Flags]
public enum Targets 
{   NotSet = 0,  Anonymous = 1, Everyone = 2,
    Adult = 4,   Child = 8,    LikesFishing = 16 }
person Charles Bretana    schedule 15.12.2010
comment
Интересно, приведение к int может сработать. Постараюсь и дам вам знать. - person eduncan911; 16.12.2010
comment
На самом деле приведение к int не нужно, к вашему сведению. - person eduncan911; 16.12.2010
comment
Я должен попробовать это, чтобы быть уверенным, но, насколько я помню, если вы этого не сделаете, вы получите сообщение об ошибке, потому что вы не можете использовать оператор › между перечислением Targets и целым числом (они не одного типа). возможно вы испытываете неявное литье... - person Charles Bretana; 16.12.2010

Во-первых, вам нужно убедиться, что вы приписываете это перечисление флагам:

[Flags]
public enum Targets
{
    NotSet = 0,

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

    LikesFishing = 16
}

Секунды, ваш LINQ будет выглядеть так:

var articlesThatLikeFishing = db.Articles.Where(x => (x.Targets & Targets.LikesFishing) == Targets.LikesFishing)
person poindexter12    schedule 15.12.2010
comment
В запросе linq вы указываете Targets.LikesFishing. Я не могу этого сделать, так как все, что у меня есть, это коллекция loggedInUser.Targets. Вот и все. - person eduncan911; 16.12.2010
comment
Flags изменяет ToString и позволяет ему взаимодействовать с другими языками CLR (vb). - person marr75; 16.12.2010

Я думаю, вы хотели украсить свой Enum декоратором флагов.

Кроме того, в зависимости от используемого поставщика linq эта функция может быть реализована или не реализована. Поставщик должен иметь возможность разбить выражение, чтобы создать соответствующее предложение where, и некоторые поставщики не могут обработать то, что вы просите. Это оставляет вам несколько вариантов.

Вы можете написать методы, которые будут генерировать выражение, которое может обработать ваш провайдер. Вероятно, это что-то вроде .Where(x => x.Targets == 1 || x.Targets = 3 или x.Targets = 5... и т. д. Ваш код не будет выглядеть так, заметьте, потому что вы будете динамически генерировать выражение (это более сложная тема, см. эту статью).

Вы можете, проще говоря, переместить перечисление целей в отдельный объект с идентификатором и описанием. Затем вы можете присоединиться к ним с отношением «многие ко многим» и запросить это.

person marr75    schedule 15.12.2010

Я буду использовать int вместо enum, что-то вроде:

    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; }
}
person QYY    schedule 15.12.2010