【问题标题】:How to check if any flags of a flag combination are set?如何检查是否设置了标志组合的任何标志?
【发布时间】:2010-11-23 07:44:45
【问题描述】:

假设我有这个枚举:

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

例如,可以将& 换成什么东西吗?

【问题讨论】:

  • 不应该全部阅读'All = A |乙| C'?
  • AB | C 等价于 A |乙| C 因为 AB 被定义为 A | B 之前。
  • @Daniel Brückner - 它是等价的,但可读性较差。特别是如果枚举被扩展。
  • 是的。我可以更改它以便更好地阅读。

标签: c# enums flags


【解决方案1】:

在 .NET 4 中,您可以使用 Enum.HasFlag method

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.

【讨论】:

  • 这并没有解决 OP 问题。您仍然必须 && 多个 HasFlag 操作来确定是否设置了 any 标志。所以问题是petsInFamily 是否有Pet.Dog || Pet.Cat
  • 看斯基特先生的明确回答...HasFlags Multiple
  • 微软说:如果枚举包含 0(零),HasFlag 工作不正确
【解决方案2】:

如果您想知道字母是否包含 AB 中的任何字母,您必须使用 AND & 运算符。比如:

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

【讨论】:

  • 据我所知,这可以完成工作。并且有最清晰的cmets。尽管在letter & Letters.AB 周围没有括号,但无法编译。在那里编辑。
  • 另外,如果我介绍了Letters.None,我想您可以将其与0 交换,以获得更少与魔术数字比较的外观?
  • 当然。但我不认为与 0 的 AND 比较严格可以被认为是一个神奇的数字。
  • stackoverflow.com/questions/40211/how-to-compare-flags-in-c 也是推荐的答案,因为它会检查有问题的项目,而不是检查它是否等于 0
  • @danrichardson 检查确切项目的问题在于它消除了设置复合值的部分(A或B)的情况,这不是 OP 想要的。
【解决方案3】:

我使用扩展方法来写这样的东西:

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

【讨论】:

  • 你可以把它弄得更紧一点:where T : struct, IConvertible。否则代码很棒!
  • @HamishGrubijan,好点......而且枚举也实现了 IFormattable 和 IComparable。但是,所有数值类型也都实现了这些接口,因此仅排除它们是不够的
  • 感谢分享,但您并不总是需要检查枚举。 IsFlagSet(this Enum value, Enum flag) 就足够了。
【解决方案4】:

.NET 4 或更高版本中有HasFlag 方法。

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

【讨论】:

    【解决方案5】:

    如果你可以使用 .NET 4 或更高版本而不是使用 HasFlag() 方法

    例子

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

    一样

    letter.HasFlag(Letters.AB)
    

    【讨论】:

    • 你确定bitwise OR 让它“都必须设置”而不是任何?
    • bitwise OR 将组合这些值,所以 1000 | 0010 变为 1010,或同时设置
    【解决方案6】:

    如果真的让你不爽,你可以写一个这样的函数:

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

    【讨论】:

    • return (value &amp; flag) == flag; 行无法编译:“运算符'&' 不能应用于'T' 和'T' 类型的操作数”.
    • awe:问题不是关于二进制操作,而是关于简化 C# 中位掩码相关操作的语法。 stackoverflow 上已经有很多优秀的二元运算相关问答,无需到处转帖。
    • 我应该建议那些不熟悉二进制操作的人熟悉一下,因为在我看来,将它隐藏在上面的脚手架实际上使事情的可读性大大降低。当然,与此解决方案的得分相比,我下面的“原始”解决方案目前表现不佳,因此人们正在投票他们的偏好,我必须尊重这一点;-)
    【解决方案7】:

    要检查是否设置了例如 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));
    

    顺便提一下,问题示例中变量的命名是单数“字母”,这可能意味着它只代表一个字母;示例代码清楚地表明它是一组可能的字母并且允许多个值,因此请考虑重命名变量“字母”。

    【讨论】:

    • anything_and_aa_and_or_c_and_anything_elseboth_ac_and_anything_else 不会总是正确的吗?还是我在这里遗漏了什么?
    • 在这种情况下,您可以看到已初始化的标志。但是,如果标志不包含 A,则 (flags & A) 将为 0,这是错误的。 both_ac_and_anything_else 确保同时设置了 A 和 C,但忽略了任何其他也设置的标志(例如,无论 B 是否设置,它都是真的)。​​
    • 嗯,其中一些以数字结尾,但在 C# 中不是布尔值。您如何将它们转换为布尔值?
    • 它不是为你隐式转换的吗?零相当于“假”,所有其他值都是“真”。
    【解决方案8】:

    我创建了一个不需要检查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));
    }
    

    享受吧!

    【讨论】:

      【解决方案9】:

      这里有很多答案,但我认为使用标志最惯用的方法是 Letters.AB.HasFlag(letter) 或 (Letters.A | Letters.B).HasFlag(letter) 如果你还没有 Letters.AB。 letter.HasFlag(Letters.AB) 仅在两者兼有时才有效。

      【讨论】:

        【解决方案10】:

        怎么样

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

        ?

        【讨论】:

        • 是的!这将过滤 A 和 B 值,如果包含 C 则忽略。所以如果它>0,它也是A或B或AB。
        • 这不能 100% 使用带符号的值。由于这个原因,!= 0 优于 > 0。
        【解决方案11】:

        您可以在枚举上使用此扩展方法,适用于任何类型的枚举:

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

        【讨论】:

        • 认为这是我读到的第一个真正正确回答问题的答案。最终这基本上是我要做的,但我希望有一个内置的方法来检查单个标志实例。
        【解决方案12】:

        这对你有用吗?

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

        【讨论】:

          【解决方案13】:
          if((int)letter != 0) { }
          

          【讨论】:

          • 你可能和我犯了同样的错误 - 他想检查是否设置了 A 或 B 但忽略了 C。
          • 如果您根据 0 检查枚举,则不需要演员表。
          • 这将检查是否设置了所有这些,而不是设置了任何组合枚举。
          【解决方案14】:

          您可以检查该值是否不为零。

          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 检查枚举,则不需要演员表。
          【解决方案15】:

          我可以看到有两种方法可以用于检查是否设置了任何位。

          方法 A

          if (letter != 0)
          {
          }
          

          只要您不介意检查 所有 位,包括未定义的位,它就可以工作!

          方法 B

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

          这只检查定义的位,只要 Letters.All 代表所有可能的位。

          对于特定位(一组或多组),使用方法 B 将 Letters.All 替换为您要检查的位(见下文)。

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

          【讨论】:

          • 你可能和我犯了同样的错误 - 他想检查是否设置了 A 或 B 但忽略了 C。
          【解决方案16】:

          从 .Net 4 开始,您可以使用简写版本而无需明确指定 &:

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

          【讨论】:

          • 微软说:如果枚举包含 0(零),HasFlag 工作不正确
          • @RamilAliyev:如果我没记错的话,Microsoft says:如果枚举包含 0(零),如果您使用 Instance.HasFlag(flag) 检查 0(零)标志,这将始终返回 true。要检查零标志Equals(flag),应使用方法。因此,HasFlag() 可以正确处理包含零值的枚举,但 检查零标志 不适用于 HasFlag()
          • @DhyMik 我同意你的看法
          【解决方案17】:

          我们能否轻松有效地找出是否设置了至少一个标志

          好吧,如果您对检查是否至少设置了一个标志感到满意,那么可以!

          用法:

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

          我们正在检查是否设置了至少一个 bit 标志是什么意思?

          好吧,这个解决方案可能无法正确回答以下无意义的枚举:

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

          【讨论】:

            【解决方案18】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-03-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-05-25
              • 2015-12-29
              • 2018-03-25
              • 1970-01-01
              相关资源
              最近更新 更多