簡體   English   中英

如何檢查是否設置了標志組合的任何標志?

[英]How to check if any flags of a flag combination are set?

假設我有這個枚舉:

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

例如,可以用一些東西交換&嗎?

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

如果您想知道 letter 是否包含 AB 中的任何字母,則必須使用AND &運算符。 就像是:

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

我使用擴展方法來寫這樣的東西:

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,我可以這樣做:

如果((字母和字母.AB)==字母.AB)

是否有比以下更簡單的方法來檢查是否設置了組合標志常量的任何標志?

這個檢查是A和B兩者都設置,以及是否忽略任何其他標志被設置。

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

即AB被設定此檢查,是否忽略任何其他標志被設定或沒有。

這可以簡化為:

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

順便提一下,問題示例中變量的命名是單數“字母”,這可能意味着它只代表一個字母; 示例代碼清楚地表明它是一組可能的字母並且允許多個值,因此請考慮重命名變量“字母”。

我創建了一個不需要檢查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));
}

享受!

這里有很多答案,但我認為使用 Flags 最慣用的方法是 Letters.AB.HasFlag(letter) 或 (Letters.A | Letters.B).HasFlag(letter) 如果你沒有已經有 Letters.AB。 letter.HasFlag(Letters.AB) 只有在兩者都有時才有效。

怎么樣

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

?

這對你有用嗎?

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

您可以在枚舉上使用此擴展方法,用於任何類型的枚舉:

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;
}
if((int)letter != 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) { }

更新

誤讀了問題 - 修正了第一個建議,而忽略了第二個建議。

我可以看到有兩種方法可以用於檢查是否設置了任何位。

方法A

if (letter != 0)
{
}

只要您不介意檢查所有位(包括未定義的位),此方法就有效!

方法B

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

這僅檢查定義的位,只要 Letters.All 代表所有可能的位。

對於特定位(一組或多組),使用方法 B 將 Letters.All 替換為您要檢查的位(見下文)。

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

從 .Net 4 開始,您可以使用速記版本而無需明確指定 &:

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

我們能否輕松高效地找出是否至少設置了一個標志

好吧,如果您對檢查是否至少設置了一個標志感到滿意,那么是的!

用法:

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

抱歉,但我會在 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM