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


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.