Як перевірити, чи встановлені будь-які прапори комбінації прапорів?


180

Скажімо, у мене є цей перелік:

[Flags]
enum Letters
{
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = A | B | C,
}

Щоб перевірити, чи ABвстановлено, наприклад , я можу це зробити:

if((letter & Letters.AB) == Letters.AB)

Чи є більш простий спосіб перевірити, чи встановлений будь-який з прапорів комбінованої константи прапора, ніж наступний?

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Ви можете, наприклад, обмінятись &чимось?

Не надто стійкий, коли мова йде про бінарні речі, як це ...


Чи не повинні всі читати "Усі = A |" Б | C '?
— stevehipwell

4
AB | C еквівалентно A | Б | C тому, що AB було визначено як A | Б раніше.
— Даніель Брюкнер

1
@Daniel Brückner - це рівнозначно, але воно менш читабельне. Особливо, якщо перерахунок розширився.
— stevehipwell

Правда. Я можу змінити це для кращого читання.
— Свиш

Відповіді:


145

Якщо ви хочете дізнатися, чи є в букві будь-яка з букв в AB, ви повинні використовувати оператор AND &. Щось на зразок:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}

2
Наскільки я бачу, це робить свою роботу. І мали найясніші коментарі. Не збирається, хоча без дужок навколо letter & Letters.AB. Відредагував це там.
— Свиш

Крім того, якщо я представив Letters.None, я припускаю, що ви можете поміняти це 0на вигляд, що не відповідає порівнянню з магічним числом?
— Свиш

Звичайно. Але я не думаю, що порівняння І з 0 можна вважати магічним числом строго.
— yeyeyerman

9
також stackoverflow.com/questions/40211/how-to-compare-flags-in-c є рекомендованою відповіддю, оскільки вона перевіряє відповідний предмет на відміну від перевірки, чи дорівнює він 0
— dan richardson

@danrichardson Проблема з перевіркою на точний елемент полягає в тому, що вона виключає випадок, коли встановлюється частина складеного значення (або A, або B), що не є тим, чого хоче ОП.
— Том Лінт

181

У .NET 4 ви можете використовувати метод Enum.HasFlag :

using System;

[Flags] public enum Pet {
   None = 0,
   Dog = 1,
   Cat = 2,
   Bird = 4,
   Rabbit = 8,
   Other = 16
}

public class Example
{
   public static void Main()
   {
      // Define three families: one without pets, one with dog + cat and one with a dog only
      Pet[] petsInFamilies = { Pet.None, Pet.Dog | Pet.Cat, Pet.Dog };
      int familiesWithoutPets = 0;
      int familiesWithDog = 0;

      foreach (Pet petsInFamily in petsInFamilies)
      {
         // Count families that have no pets. 
         if (petsInFamily.Equals(Pet.None))
            familiesWithoutPets++;
         // Of families with pets, count families that have a dog. 
         else if (petsInFamily.HasFlag(Pet.Dog))
            familiesWithDog++;
      }
      Console.WriteLine("{0} of {1} families in the sample have no pets.", 
                        familiesWithoutPets, petsInFamilies.Length);   
      Console.WriteLine("{0} of {1} families in the sample have a dog.", 
                        familiesWithDog, petsInFamilies.Length);   
   }
}

У прикладі відображається такий вихід:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.

14
Це не стосується питання щодо ОП. Ви все ще повинні виконати && кілька операцій HasFlag, щоб визначити, чи встановлені будь-які прапори. Отже, питання є чи petsInFamilyє Pet.Dog || Pet.Cat?
— GoClimbColorado

1
Дивіться чітку відповідь містера Скіта ... HasFlags Multiple
— GoClimbColorado

59

Я використовую методи розширення, щоб написати такі речі:

if (letter.IsFlagSet(Letter.AB))
    ...

Ось код:

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(true);
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static string GetDescription<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(false);
        string name = Enum.GetName(typeof(T), value);
        if (name != null)
        {
            FieldInfo field = typeof(T).GetField(name);
            if (field != null)
            {
                DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attr != null)
                {
                    return attr.Description;
                }
            }
        }
        return null;
    }
}

1
Ви могли б зробити його трохи тугіше , як так: where T : struct, IConvertible. Чудовий код інакше!
— Гаміш Грубіян

@HamishGrubijan, хороший момент ... і перелічі також реалізують IFormattable та IComparable. Однак усі цифрові типи також реалізують ці інтерфейси, тому їх недостатньо виключити
— Thomas Levesque

Дякуємо за те, що поділилися, але не завжди потрібно перевіряти перелік. IsFlagSet(this Enum value, Enum flag)є достатнім.
— djmj


26

Якщо ви можете використовувати .NET 4 або вище, використовуйте метод HasFlag ()

приклади

letter.HasFlag(Letters.A | Letters.B) // both A and B must be set

такий же, як

letter.HasFlag(Letters.AB)

Ви впевнені, що bitwise ORце "обидва повинні бути встановлені", а не будь-які?
— Дужки

1
bitwise ORпоєднав би значення, тому 1000 | 0010 стає 1010, або обидва встановлені
— Армандо

13

Якщо це насправді дратує вас, ви можете написати таку функцію:

public bool IsSet(Letters value, Letters flag)
{
    return (value & flag) == flag;
}

if (IsSet(letter, Letters.A))
{
   // ...
}

// If you want to check if BOTH Letters.A and Letters.B are set:
if (IsSet(letter, Letters.A & Letters.B))
{
   // ...
}

// If you want an OR, I'm afraid you will have to be more verbose:
if (IsSet(letter, Letters.A) || IsSet(letter, Letters.B))
{
   // ...
}

1
Рядок return (value & flag) == flag;не компілюється: "Оператор" та "не можна застосовувати до операндів типу" Т "і" Т "" .
— Фредрік Морк

1
Побоювання: Питання було не про бінарні операції, а про спрощення синтаксису операцій, пов’язаних з бітними масками в C #. На stackoverflow вже існує безліч відмінних запитань і відповідей, пов’язаних з двійковою операцією, не потрібно їх репостувати скрізь.
— Тамас Цінеге

Я повинен рекомендувати ознайомитися з незнайомими бінарними операціями, оскільки ліси, щоб приховати це вище, насправді роблять речі набагато менш читабельними. Звичайно, моє «сире» рішення нижче не так добре в порівнянні з оцінкою цього рішення, тому люди голосують за свої вподобання, і я повинен це поважати ;-)
— Буде

10

Щоб перевірити, чи встановлено, наприклад, AB, я можу це зробити:

якщо ((лист та букви.АБ) == Букви.АБ)

Чи є більш простий спосіб перевірити, чи встановлений будь-який з прапорів комбінованої константи прапора, ніж наступний?

Це перевіряє, що встановлені і A, і B, і ігнорує, чи встановлені будь-які інші прапори.

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Це перевіряє, чи встановлено або A, або B, і ігнорує, встановлені чи ні інші прапори.

Це можна спростити до:

if(letter & Letters.AB)

Ось С для бінарних операцій; слід застосувати це до C #:

enum {
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = AB | C,
};

int flags = A|C;

bool anything_and_a = flags & A;

bool only_a = (flags == A);

bool a_and_or_c_and_anything_else = flags & (A|C);

bool both_ac_and_anything_else = (flags & (A|C)) == (A|C);

bool only_a_and_c = (flags == (A|C));

Між іншим, найменування змінної у прикладі запитання є одниною "буква", що може означати, що вона являє собою лише одну букву; у прикладі коду стає зрозумілим, що його набір можливих літер і що дозволяється декілька значень, тому розгляньте можливість перейменування змінної "літери".


Не буде anything_and_a, a_and_or_c_and_anything_elseі both_ac_and_anything_elseзавжди бути правдою? чи я щось тут пропускаю?
— Свиш

У цьому випадку ви можете побачити, до яких прапорів було ініціалізовано. Однак, якщо прапори не містять A, тоді (прапори & A) буде 0, що хибно. obo_ac_and_anything_else гарантує встановлення і A, і C, але ігнорує будь-які інші прапори, які також встановлені (наприклад, його правда, встановлено B чи ні).
— Буде

Гм, деякі з них закінчуються як числа, а не булеві в C #, хоча. Як би ви перетворили їх на булеві?
— Свиш

Це для вас неявно не перетворено? Нуль еквівалентно «помилковому», а всі інші значення - «істинні».
— Буде

4

Як щодо

if ((letter & Letters.AB) > 0)

?


Так! Це дозволить фільтрувати значення A і B і ігнорувати, якщо включено C. Отже, якщо це> 0, це також A або B або AB.
— трепет

3
Це не працює на 100% із підписаними значеннями. ! = 0 з цієї причини краще, ніж> 0.
— stevehipwell

4

Я створив простий метод розширення, який не потребує перевірки Enumтипів:

public static bool HasAnyFlag(this Enum value, Enum flags)
{
    return
        value != null && ((Convert.ToInt32(value) & Convert.ToInt32(flags)) != 0);
}

Він також працює на нульових перерахунках. Стандартного HasFlagметоду немає, тому я створив розширення, щоб охопити і це.

public static bool HasFlag(this Enum value, Enum flags)
{
    int f = Convert.ToInt32(flags);

    return
        value != null && ((Convert.ToInt32(value) & f) == f);
}

Простий тест:

[Flags]
enum Option
{
    None = 0x00,
    One = 0x01,
    Two = 0x02,
    Three = One | Two,
    Four = 0x04
}

[TestMethod]
public void HasAnyFlag()
{
    Option o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

[TestMethod]
public void HasAnyFlag_NullableEnum()
{
    Option? o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

Насолоджуйтесь!


4

Тут є багато відповідей, але я думаю, що найбільш ідіоматичним способом зробити це за допомогою прапорів було б Letters.AB.HasFlag (letter) або (Letters.A | Letters.B). вже є Letters.AB. letter.HasFlag (Letters.AB) працює лише за наявності обох.



1

Ви можете використовувати цей метод розширення для enum, для будь-якого типу перерахунків:

public static bool IsSingle(this Enum value)
{
    var items = Enum.GetValues(value.GetType());
    var counter = 0;
    foreach (var item in items)
    {
        if (value.HasFlag((Enum)item))
        {
            counter++;
        }
        if (counter > 1)
        {
            return false;
        }
    }
    return true;
}

0
if((int)letter != 0) { }

Ви можете зробити ту саму помилку, що і я - він хоче перевірити, чи встановлено A або B, але ігнорувати C.
— Даніель Брюкнер

Вам не потрібен акторський склад, якщо ви перевіряєте перерахунок на 0.
— stevehipwell

Це дозволить перевірити, чи встановлено будь-який із них, а не встановлено будь-який з комбінованих перерахунків.
— Свиш

0

Ви можете просто перевірити, чи значення не дорівнює нулю.

if ((Int32)(letter & Letters.AB) != 0) { }

Але я вважаю б кращим рішенням ввести нове значення перерахування зі значенням нульовим і порівняти отримане це значення перерахування (якщо можливо, тому що ви повинні бути в змозі змінити перерахування).

[Flags]
enum Letters
{
    None = 0,
    A    = 1,
    B    = 2,
    C    = 4,
    AB   =  A | B,
    All  = AB | C
}

if (letter != Letters.None) { }

ОНОВЛЕННЯ

Прочитайте питання - виправте першу пропозицію та просто ігноруйте другу пропозицію.


Вам не потрібен акторський склад, якщо ви перевіряєте перерахунок на 0.
— stevehipwell

0

Є два підходи, які я можу побачити, які допоможуть перевірити наявність будь-якого біта.

Захист А

if (letter != 0)
{
}

Це працює до тих пір, поки ви не проти перевірити всі біти, в тому числі і не визначені!

Поступ B

if ((letter & Letters.All) != 0)
{
}

Це перевіряє лише визначені біти, якщо Letters.All представляє всі можливі біти.

Для конкретних бітів (один або декілька наборів) використовуйте Aproach B замінюючи Letters.All з бітами, які ви хочете перевірити (див. Нижче).

if ((letter & Letters.AB) != 0)
{
}

Ви можете зробити ту саму помилку, що і я - він хоче перевірити, чи встановлено A або B, але ігнорувати C.
— Даніель Брюкнер

-1

Вибачте, але я покажу це в VB :)

   <Flags()> Public Enum Cnt As Integer
        None = 0
        One = 1
        Two = 2
        Three = 4
        Four = 8    
    End Enum

    Sub Test()
    Dim CntValue As New Cnt
    CntValue += Cnt.One
    CntValue += Cnt.Three
    Console.WriteLine(CntValue)
    End Sub

CntValue = 5 Отже, перерахунок містить 1 + 4

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.