Есть ли способ узнать, есть ли в перечислении ровно один, несколько или нет флагов?
У меня есть перечисление, определенное так:
[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;
- Если
v == 0
тогда никакие флаги не установлены - В противном случае, если
((v-1)&v) == 0
тогда устанавливается ровно один флаг - В противном случае устанавливается несколько флагов.
Единственный хитрый - №2. Вот объяснение: рассмотрим двоичное число с ровно одним битом, установленным в 1
, Затем вычитая 1
сделает следующее изменение:
0000010000000
- 1
-------------
0000001111111
Все нули, следующие за одиноким 1
становиться 1
s, 1
становится равным нулю, а остальные биты остаются прежними. AND
-ную v
а также v-1
производит ноль, потому что нет пары 1
s в одинаковом положении между двумя числами.
Когда есть два или более 1
s, биты слева от одинокого 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);