Есть ли способ узнать, есть ли в перечислении ровно один, несколько или нет флагов?

У меня есть перечисление, определенное так:

[Flags]
public enum Orientation
{
    North = 1,
    North_East = 2,
    East = 4,
    South_East = 8,
    South = 16,
    South_West = 32,
    West = 64,
    North_West = 128
}

Есть ли общий способ узнать, установлен ли ровно один флаг, несколько или нет? Меня не волнует значение enum, я просто хочу знать, сколько флагов установлено.

Это не дубликат для подсчета количества бит. Если я инициализирую перечисление вот так и подсчитываю количество битов, которые я получаю 10. Но количество установленных флагов будет равно 8.

GeographicOrientation go = (GeographicOrientation) 1023;

6 ответов

Решение

Вы можете использовать этот код:

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

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

int v = (int) enumValue;

  1. Если v == 0тогда никакие флаги не установлены
  2. В противном случае, если ((v-1)&v) == 0тогда устанавливается ровно один флаг
  3. В противном случае устанавливается несколько флагов.

Единственный хитрый - №2. Вот объяснение: рассмотрим двоичное число с ровно одним битом, установленным в 1, Затем вычитая 1 сделает следующее изменение:

  0000010000000
-             1
  -------------
  0000001111111

Все нули, следующие за одиноким 1 становиться 1s, 1 становится равным нулю, а остальные биты остаются прежними. AND-ную v а также v-1 производит ноль, потому что нет пары 1s в одинаковом положении между двумя числами.

Когда есть два или более 1s, биты слева от одинокого 1 останется без изменений. Поэтому, по крайней мере, одна позиция будет иметь 1 в результате побитового AND,

var test = Orientation.North;
var flagCount = GetFlagCount(test);

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

Если вы ищете "кратчайший" путь:

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

или даже короче

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

Обновить

Чтобы поддерживать значения выше 255, вы можете использовать любой из этих уродливых хаков:

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

или просто определите перечисление как байт:

    [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 Лично я бы не использовал строковый метод в производственном коде, особенно когда требуется только немного подсчета.

Во всяком случае, я просто подумал о другом взломе просто для удовольствия. Журнал Base 2 будет возвращать целое число, когда установлен один бит, -Infinity, когда 0, и все остальное, если установлено более одного бита. Например

 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 проверить, установлен ли только один флаг.

Но решение dasblinkenlight кажется лучшим.

С верхней части моей головы:

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
}

Это не дубликат для подсчета количества бит. Если я инициализирую перечисление вот так и подсчитываю количество битов, которые я получаю 10. Но количество установленных флагов будет равно 8.

Пожалуйста, рассмотрите это утверждение непосредственно из MSDN:

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

Если вы разрабатываете или используете enum каким-либо образом, который НЕ позволяет подсчитывать количество флагов с помощью побитовых операций, то вы неправильно проектируете или используете enum.

    [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

Если вы правильно спроектируете перечисление, вы можете посчитать флаг и, возможно, короткое замыкание, если установлено более одного флага, так как продолжение подсчета будет бессмысленным.

        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);
Другие вопросы по тегам