如何检查是否设置了标志组合的标志?


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)

例如,您可以将它换成&其他东西吗?

像这样的二进制东西不太稳定...


不应该全部读'All = A | B | C'?
stevehipwell,2009年

4
AB | C等于A | B | C是因为AB被定义为A | B之前。
DanielBrückner09年

1
@DanielBrückner-这是等效的,但可读性较差。特别是如果枚举扩展了。
stevehipwell,2009年

真正。我可以更改它以便更好地阅读。
Svish

Answers:


141

如果您想知道字母是否包含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。在那编辑。
Svish

另外,如果我引入了a Letters.None,我假设您可以将其替换为,以使0magic-with-magic-number的外观更少?
Svish

当然。但是我不认为严格将与0的AND比较视为魔术数字。
yeyeyerman

9
还是stackoverflow.com/questions/40211/how-to-compare-flags-in-c是一个推荐的答案,因为它会检查有问题的项目,而不是检查它是否等于0
dan richardson

@danrichardson检查确切项目的问题在于,它消除了设置部分复合值(A或B)的情况,这不是OP想要的。
Tom Lint 2014年

179

在.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
这不能解决OP问题。您仍然必须&&进行多个HasFlag操作,以确定是否设置了任何标志。所以问题是petsInFamily有没有Pet.Dog || Pet.Cat
GoClimbColorado

1
请参阅Skeet先生的明确答案。。。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。否则,很棒的代码!
Hamish Grubijan 2012年

@HamishGrubijan,很好。。。枚举还实现了IFormattable和IComparable。但是,所有数值类型也都实现了这些接口,因此仅将它们排除是不够的
Thomas Levesque 2012年

感谢您的分享,但您不必总是检查枚举。IsFlagSet(this Enum value, Enum flag)足够了。
djmj 2014年


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;无法编译:“运算符'&'不能应用于类型'T'和'T'的操作数”
2009年

1
敬畏:问题不是关于二进制运算,而是关于简化C#中与位掩码相关的运算的语法。在stackoverflow上已经有很多与二进制操作相关的优秀问题和解答,无需在任何地方重新发布它们。
2009年

我应该建议那些不熟悉二进制操作的人熟悉,因为将其隐藏在上面的脚手架实际上会使我难以理解。当然,我的“原料”以下解决方案目前没有做得很好相比,该解决方案的得分,所以人们投票自己的喜好,我必须尊重这一点;-)
威尔

10

要检查例如是否设置了AB,我可以这样做:

如果((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_else而且both_ac_and_anything_else永远是真的吗?还是我在这里想念东西?
Svish

在这种情况下,您可以查看已初始化的标志。但是,如果标志不包含A,则(标志&A)将为0,这是错误的。both_ac_and_anything_else确保同时设置了A和C,但忽略了也设置的任何其他标志(例如,是否设置了B,则为true)。
威尔

嗯,其中一些以数字结尾,但是在C#中不是布尔值。您如何将它们转换为布尔值?
Svish

它不是为您隐式转换的吗?零等于“假”,所有其他值均为“真”。
威尔


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

这里有很多答案,但我认为使用Flags进行此操作最惯用的方法是Letters.AB.HasFlag(letter)或(Letters.A | Letters.B).HasFlag(letter)(如果没有)已经有Letters.AB。letter.HasFlag(Letters.AB)仅在同时具有两者的情况下才起作用。



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

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

你还不如我做了同样的错误-他要检查,如果A或B组,但忽略C.
丹尼尔·布鲁克纳

你并不需要,如果你检查,对0枚举投
stevehipwell

这将检查是否设置了所有这些枚举,而不是设置了任何组合枚举。
Svish

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

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

方式A

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.