Как проверить, установлены ли какие-либо флаги комбинации флагов?

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)

Не могли бы вы, например, поменяться &с чем-то?

Не слишком стабильный, когда дело доходит до бинарных вещей вроде этого ...

Svish
источник
Не должны ли все читать 'All = 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
}
yeyeyerman
источник
2
Насколько я вижу, это делает работу. И имел самые четкие комментарии. Не компилируется, хотя без круглых скобок letter & Letters.AB. Отредактировал это там.
Свиш
Кроме того, если я представлю Letters.None, я предполагаю, что вы могли бы поменять это с 0менее похожим на магическое число?
Свиш
Конечно. Но я не думаю, что сравнение AND с 0 можно считать строго магическим числом.
yeyeyerman
9
также stackoverflow.com/questions/40211/how-to-compare-flags-in-c является рекомендуемым ответом, так как он проверяет соответствующий элемент, а не проверяет, равен ли он 0
Дэн Ричардсон
@danrichardson проблема с проверкой для точного элемента состоит в том, что он исключает случай, когда установлена часть составного значения (или A, или B), а это не то, что хочет OP.
Том Линт
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. Тем не менее, все числовые типы также реализуют эти интерфейсы, поэтому их недостаточно исключить
Томас Левеск,
Спасибо, что поделились, но вам не всегда нужно проверять enum. IsFlagSet(this Enum value, Enum flag)достаточно.
djmj
34

Есть метод HasFlag в .NET 4 или выше.

if(letter.HasFlag(Letters.AB))
{
}
Artru
источник
26

Если вы можете использовать .NET 4 или выше, чем использовать метод HasFlag ()

Примеры

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

такой же как

letter.HasFlag(Letters.AB)
Luka
источник
Вы уверены, что 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;не компилируется: «Оператор» & «не может быть применен к операндам типа« T »и« T »» .
Фредрик Мёрк
1
Awe: Вопрос был не о бинарных операциях, а об упрощении синтаксиса операций, связанных с битовой маской в ​​C #. Уже есть множество отличных вопросов и ответов, связанных с бинарными операциями, и нет необходимости перепечатывать их повсюду.
Тамас Чинеге
Я должен порекомендовать тем, кто не знаком с бинарными операциями, ознакомиться, так как, по моему мнению, леса, скрывающие это выше, на самом деле делают вещи намного менее читабельными. Конечно, мое «сырое» решение, представленное ниже, в настоящее время не так хорошо по сравнению с оценкой этого решения, поэтому люди голосуют за свои предпочтения, и я должен уважать это ;-)
Will
10

Чтобы проверить, установлен ли, например, AB, я могу сделать это:

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

Есть ли более простой способ проверить, установлены ли какие-либо флаги объединенной постоянной флага, чем следующие?

Это проверяет, что и A и B установлены, и игнорирует, установлены ли какие-либо другие флаги.

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

Это проверяет, установлен ли A или B, и игнорирует, установлены ли какие-либо другие флаги или нет.

Это может быть упрощено до:

if(letter & Letters.AB)

Вот C для бинарных операций; должно быть просто применить это к 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));

Кстати, именование переменной в примере вопроса - это единственное «письмо», что может означать, что оно представляет только одну букву; код примера проясняет, что это набор возможных букв и что допускается несколько значений, поэтому рассмотрите возможность переименования переменной 'letters'.

Будет
источник
Не было anything_and_a, a_and_or_c_and_anything_elseи both_ac_and_anything_elseвсегда быть правдой? или я что-то здесь упускаю?
Свиш
В этом случае вы можете увидеть, для чего были инициализированы флаги. Однако, если флаги не содержат A, тогда (flags & A) будет 0, что ложно. both_ac_and_anything_else гарантирует, что оба A и C установлены, но игнорирует любые другие флаги, которые также установлены (например, его true независимо от того, установлен 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);
}

Он также работает со значениями nullable. Стандартного 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

Здесь есть много ответов, но я думаю, что самый идиоматический способ сделать это с Flags - это Letters.AB.HasFlag (letter) или (Letters.A | Letters.B) .HasFlag (letter), если вы этого не сделали уже есть Letters.AB. letter.HasFlag (Letters.AB) работает, только если у него есть оба.

Novaterata
источник
3

Будет ли это работать для вас?

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

С Уважением,

Sebastiaan

Себастьян М
источник
1

Вы можете использовать этот метод расширения для перечислений для любого типа перечислений:

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;
}
masehhat
источник
0
if((int)letter != 0) { }
подветренный
источник
Вы можете совершить ту же ошибку, что и я, - он хочет проверить, установлены ли A или B, но игнорировать C.
Даниэль Брюкнер,
Вам не нужен актерский состав, если вы проверяете enum против 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) { }

ОБНОВИТЬ

Пропустил вопрос - исправил первое предложение и просто проигнорировал второе.

Даниэль Брюкнер
источник
Вам не нужен актерский состав, если вы проверяете enum против 0.
Stevehipwell
0

Я вижу два подхода, которые сработали бы для проверки любого установленного бита.

Подход А

if (letter != 0)
{
}

Это работает, если вы не против проверить все биты, в том числе и неопределенные!

Подход B

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

Это проверяет только определенные биты, пока Letters.All представляет все возможные биты.

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

if ((letter & Letters.AB) != 0)
{
}
stevehipwell
источник
Вы можете совершить ту же ошибку, что и я, - он хочет проверить, установлены ли 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

Wiroko
источник