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

Допустим, у меня есть этот enum:

[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)

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

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

19 ответов

Решение

Если вы хотите узнать, содержит ли буква какие-либо буквы в AB, вы должны использовать оператор AND &. Что-то вроде:

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

В.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.

Я использую методы расширения для написания таких вещей:

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

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

if(letter.HasFlag(Letters.AB))
{
}

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

Примеры

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

такой же как

letter.HasFlag(Letters.AB)

Если вас это действительно раздражает, вы можете написать такую ​​функцию:

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))
{
   // ...
}

Чтобы проверить, установлен ли, например, 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'.

Как насчет

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

?

Я создал простой метод расширения, который не нуждается в проверке 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));
}

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

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

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

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

Начиная с .Net 4, вы можете использовать сокращенную версию без явного указания &:

      if(Letters.AB.HasFlag(Letters.C))

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

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

С Уважением,

Sebastiaan

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

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

Подход А

if (letter != 0)
{
}

Это работает до тех пор, пока вы не возражаете против проверки всех битов, в том числе и неопределенных!

Подход B

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

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

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

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

Вы можете использовать побитовый оператор AND (&) непосредственно для константы комбинированного флага и проверить, не равен ли результат нулю.

Вот пример использования флага AB из вашего перечисления Letters:

      if ((letter & Letters.AB) != 0)
{
    // AB flag is set
}

Это работает, потому что побитовая операция И даст ненулевой результат только в том случае, если хотя бы один из флагов установлен в буквах. AB задается в буквенной переменной. Если результат равен нулю, это означает, что ни один из флагов в Letters не установлен. AB задаются буквой.

Вы можете применить тот же подход для проверки любой другой константы комбинированного флага, например All:

      if ((letter & Letters.All) != 0)
{
    // At least one flag from All is set
}

Используя таким образом побитовый оператор AND, вы можете упростить проверку и избежать явного сравнения с самой константой флага.

Можем ли мы легко и эффективно узнать, установлен ли хотя бы один флаг?

Ну, если вы удовлетворены проверки , является ли , по крайней мере один флаг бит установлен, то да!

Использование:

      if (EnumHelper.HasAnyFlagBitsSet(letter))

Реализация:

      public static class EnumHelper
{
    static EnumHelper()
    {
        // Required to get correct behavior in GetNumericValue
        // Because we will overlap the enum type with a ulong, left-aligned
        if (!BitConverter.IsLittleEndian)
            throw new NotSupportedException("This type is only supported on little-endian architectures.");
    }

    /// <summary>
    /// <para>
    /// Returns whether the given enum value has any bits set that occurs in a defined flag for <typeparamref name="T"/>.
    /// </para>
    /// <para>
    /// Throws if the type parameter is not an enum type with the <see cref="FlagsAttribute"/>.
    /// </para>
    /// </summary>
    public static bool HasAnyFlagBitsSet<T>(T enumValue)
        where T : unmanaged, Enum
    {
        var numericValue = GetNumericValue(enumValue);

        // Take the value that has all the permitted bits set
        // Use & to keep only the corresponding bits from the input value
        // Check that the input value provided at least one such bit
        return (numericValue & FlagValueCache<T>.AllFlagsSetValue) != 0;
    }

    /// <summary>
    /// <para>
    /// Returns whether the given enum value has any bits set that are set in <paramref name="flags"/>.
    /// </para>
    /// <para>
    /// Throws if the type parameter is not an enum type with the <see cref="FlagsAttribute"/>.
    /// </para>
    /// </summary>
    public static bool HasAnyFlagBitsSet<T>(T enumValue, T flags)
        where T : unmanaged, Enum
    {
        var numericValue = GetNumericValue(enumValue);
        var numericFlags = GetNumericValue(flags);

        // Use & to keep only the bits present in flags
        // Check that the input value provided at least one such bit
        return (numericValue & flags) != 0;
    }

    // Actually, have a bonus method as well, since this is a common operation:

    /// <summary>
    /// <para>
    /// Returns whether the given enum value consists exclusively of defined flags for <typeparamref name="T"/>.
    /// The result is false if a bit is set that is not part of any value defined by <typeparamref name="T"/>.
    /// </para>
    /// <para>
    /// Throws if the type parameter is not an enum type with the <see cref="FlagsAttribute"/>.
    /// </para>
    /// </summary>
    public static bool HasDefinedFlags<T>(T enumValue)
        where T : unmanaged, Enum
    {
        var numericValue = GetNumericValue(enumValue);

        // Take the value that has all the permitted bits set
        // Use ~ to get a value with all the forbidden bits set
        // Use & to keep only the corresponding bits from the input value
        // Check that the input value provided no such forbidden bits
        return (numericValue & ~FlagValueCache<T>.AllFlagsSetValue) == 0;
    }

    /// <summary>
    /// <para>
    /// Returns the numeric value of the given <paramref name="enumValue"/>.
    /// </para>
    /// <para>
    /// The resulting <see cref="ulong"/> can be cast to the intended integral type, even if it is a signed type.
    /// </para>
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ulong GetNumericValue<T>(T enumValue)
        where T : unmanaged, Enum
    {
        Span<ulong> ulongSpan = stackalloc ulong[] { 0UL };
        Span<T> span = MemoryMarshal.Cast<ulong, T>(ulongSpan);

        span[0] = enumValue;

        return ulongSpan[0];
    }

    /// <summary>
    /// Statically caches a "full" flags value each enum type for which this class is accessed.
    /// </summary>
    internal static class FlagValueCache<T>
        where T : unmanaged, Enum
    {
        /// <summary>
        /// Each bit that is set in any of the type's defined values is also set in this value.
        /// </summary>
        public static ulong AllFlagsSetValue { get; }

        static FlagValueCache()
        {
            if (typeof(T).BaseType != typeof(Enum)) throw new Exception("The type parameter must be an enum type.");

            foreach (var value in (T[])Enum.GetValues(typeof(T)))
                AllFlagsSetValue |= GetNumericValue(value);
        }
    }
}

Что означает, что мы проверяем, установлен ли хотя бы один бит флага ?

Что ж, это решение может не отвечать правильно на бессмысленные перечисления вроде следующего:

      [Flags]
public enum Nonsense
{
    One = 1,

    // Eh, why does this value need TWO bits when those bits are NOT defined as individual values?
    TwoAndFour = 2 | 4,
}

Здесь, EnumHelper.HasAnyFlagBitSet((Nonsense)2) вернется true, что технически некорректно, так как 2 не является определенным флагом.

Однако он отлично работает для всех разумных перечислений флагов, в том числе с несколькими флагами:

      [Flags]
public enum Fine
{
    One = 1,
    Two = 2,
    Four = 4,

    // Fine, and sensible, since these flags exist individually
    TwoAndFour = 2 | 4,
}

Вы можете просто проверить, не равно ли значение нулю.

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) { }

ОБНОВИТЬ

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

Извините, но я покажу это в 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

Другие вопросы по тегам