关于C#:有没有一种方法可以判断一个枚举是否恰好设置了一个、多个或没有标志?

Is there a way to tell if an enum has exactly one, multiple or no flags set?

我有一个这样定义的枚举:

1
2
3
4
5
6
7
8
9
10
11
12
[Flags]
public enum Orientation
{
    North = 1,
    North_East = 2,
    East = 4,
    South_East = 8,
    South = 16,
    South_West = 32,
    West = 64,
    North_West = 128
}

是否有一种通用的方法来判断是否只设置了一个标志、多个标志或无标志?我不关心枚举的值是什么,我只想知道设置了多少个标志。

这不是用于计算位数的副本。如果我像这样初始化枚举并计算得到的位数10。但设置标志的数量将是8个。

1
GeographicOrientation go = (GeographicOrientation) 1023;


您可以使用此代码:

1
2
3
4
5
6
7
var item = Orientation.North | Orientation.South;
int i = 0;
foreach (Orientation e in Enum.GetValues(typeof(Orientation)))
    if(item.HasFlag(e))
        i++;

Console.WriteLine(i);


1
2
3
4
5
6
7
var test = Orientation.North;
var flagCount = GetFlagCount(test);

public int GetFlagCount(Enum testValue)
{
    return Enum.GetValues(testValue.GetType()).Cast<Enum>().Count(testValue.HasFlag);
}


如果您正在寻找"最短"的方法:

1
2
Orientation o = Orientation.East | Orientation.West;   // o.ToString() ="East, West"
var c = o.ToString().Split().Count();

或者更短:

1
var c = (o +"").Split().Count();

更新

要支持高于255的值,您可以使用任何这些丑陋的黑客:

1
2
3
Orientation o = (Orientation) 1023;  
var c = ((Orientation)(byte)o +"").Split().Count();
c = ((Orientation)((int)o & 255) +"").Split().Count();

或者将枚举定义为字节:

1
2
3
4
5
6
7
8
9
10
11
12
    [Flags]
    public enum Orientation : byte
    {
        North = 1,
        North_East = 2,
        East = 4,
        South_East = 8,
        South = 16,
        South_West = 32,
        West = 64,
        North_West = 128
    }

更新2我个人不会在生产代码中使用string方法,特别是在只需要计算一点的时候。

不管怎样,我只是想到了另一个黑客只是为了好玩。以2为基数的日志将在设置一个位时返回一个整数,0时返回无穷大,设置多个位时返回任何其他值。例如

1
2
3
 Math.Log(0, 2 ) // -Infinity
 Math.Log(0, 64) // 6.0
 Math.Log(0, 65) // 6.0223678130284544

因此,可以使用(byte)go != 0检查是否设置了任何标志,然后使用Math.Log((byte)go, 2) % 1 == 0检查是否只设置了一个标志。

但是,达斯布林肯利特的解决方案似乎是最好的。


在我的头顶上:

1
2
3
4
5
6
7
8
9
var map = 1;
var count = 0;

while (map <= North_West)
{
    if( ((int)Value & map) > 0)
       count += 1;
    map = map << 1; //left shift, a.k.a. multiply by 2
}

This is not a duplicate for counting the number of bits. If I initialize the enum like this and count the number of bits I get 10. But the number of set flags would be 8.

请直接考虑来自msdn的声明:

Flags enumerations are used for masking bit fields and doing bitwise comparisons.

如果您以某种方式设计或使用枚举,而不允许使用按位操作计算标志数,则说明您设计或使用枚举不正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    [Flags]
    public enum MyEnum
    {
        Foo = 1,
        Bar = 2,
        Bizz = 4,
        Buzz = 8,
        Boo = 16
    }


var foo = MyEnum.Foo | MyEnum.Foo | MyEnum.Bizz;
// foo == MyEnum.Foo | MyEnum.Bizz because setting the same flag twice does nothing

var bar = (MyEnum)27 // Some invalid combination
// bar == 27.  Why would you do this instead of MyEnum.Boo | MyEnum.Buzz | MyEnum.Bar | MyEnum.Foo

如果正确设计枚举,则可以对标志进行计数,如果设置了多个标志,则可以选择短路,因为继续计数将是无意义的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        var foo = MyEnum.Foo | MyEnum.Bizz;
        int fooValue = (int)foo;
        int numberOfFlags = 0;

        while (fooValue > 0 && numberOfFlags < 2) // Stop counting if more than one flag since we don't need exact count
        {
            if ((fooValue & 1) == 1)
            {
                numberOfFlags++;
            }

            fooValue >>= 1;
        }

        Console.WriteLine(numberOfFlags);


在将值转换为int后,您可以使用简单的位技巧来确定这一点:

int v=(int)枚举值;

  • 如果是v == 0,则不设置标志。
  • 否则,如果((v-1)&v) == 0,则只设置一个标志。
  • 否则,将设置多个标志。
  • 唯一棘手的是2。这里有一个解释:考虑一个二进制数,它恰好有一个位设置为1。然后减去EDOCX1[3]将进行以下更改:

    1
    2
    3
    4
      0000010000000
    -             1
      -------------
      0000001111111

    单独的1之后的所有零变为11变为零,其余的位保持不变。ANDing vv-1产生零,因为这两个数字之间没有一对位于同一位置的1

    当存在两个或多个1时,单独1左边的位将保持不变。因此,在按位AND的结果中,至少有一个位置将具有1