Время от времени я вижу следующее перечисление:
[Flags]
public enum Options
{
None = 0,
Option1 = 1,
Option2 = 2,
Option3 = 4,
Option4 = 8
}
Я не понимаю, что именно делает атрибут [Flags].
У кого-нибудь есть хорошее объяснение или пример, который они могли бы опубликовать?
Обратите внимание, в настоящее время в VB не требуется. Сохранить поведение как C# - просто изменяет вывод ToString (). Обратите внимание: вы также можете выполнить логическое ИЛИ ВНУТРИ самого Enum. Очень круто. Кот = 1, Собака = 2, CatAndDog = Кот || Собака.
@Chalky Вы имеете в виду CatAndDog = Cat | Dog (логическое ИЛИ вместо условного), я полагаю?
@DdW, частично верно: | следует использовать, но | называется двоичным ИЛИ. II - это логическое ИЛИ (которое допускает короткое замыкание): по крайней мере, согласно Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx





Флаги позволяют вам использовать битовую маску внутри вашего перечисления. Это позволяет комбинировать значения перечисления, сохраняя при этом те, которые указаны.
[Flags]
public enum DashboardItemPresentationProperties : long
{
None = 0,
HideCollapse = 1,
HideDelete = 2,
HideEdit = 4,
HideOpenInNewWindow = 8,
HideResetSource = 16,
HideMenu = 32
}
Это неверно, вы можете использовать битовую маску, даже если перечисление не помечено как Flags.
См. Следующий пример, демонстрирующий декларацию и возможное использование:
namespace Flags
{
class Program
{
[Flags]
public enum MyFlags : short
{
Foo = 0x1,
Bar = 0x2,
Baz = 0x4
}
static void Main(string[] args)
{
MyFlags fooBar = MyFlags.Foo | MyFlags.Bar;
if ((fooBar & MyFlags.Foo) == MyFlags.Foo)
{
Console.WriteLine("Item has Foo flag set");
}
}
}
}
Этот пример работает, даже если вы не укажете [Флаги]. Это часть [Флаги], о которой я пытаюсь узнать.
Вы также можете сделать это
[Flags]
public enum MyEnum
{
None = 0,
First = 1 << 0,
Second = 1 << 1,
Third = 1 << 2,
Fourth = 1 << 3
}
Я считаю, что сдвиг битов проще, чем набирать 4,8,16,32 и так далее. Это не влияет на ваш код, потому что все это делается во время компиляции.
Единственное, что мне не нравится в этих ярлыках, - это то, что вы случайно сохранили эти перечисления в базе данных как целые числа. Затем вам придется доставать калькулятор гораздо чаще, чем один раз, чтобы выяснить, какие значения соответствуют какому перечислению при анализе данных.
@JeremyWeir Можете ли вы объяснить, чем это будет отличаться от использования 1, 2 , 4, 8, 16, ...? Также в калькуляторе Windows 7 есть биты, на которые вы можете нажимать.
Вот что я имею в виду, я предпочитаю иметь полное int в исходном коде. Если у меня есть столбец в таблице в базе данных с именем MyEnum, в котором хранится значение одного из перечислений, а запись содержит 131072, мне нужно будет достать свой калькулятор, чтобы выяснить, что соответствует перечислению со значением 1
«Все это делается во время компиляции», этот тип комментария может быть интересен некоторым, но я нахожу его разочаровывающим, потому что он подразумевает, что, скажем, 20 битовых сдвигов во время выполнения будут иметь какое-либо измеримое влияние на производительность. Процессоры очень хорошо справляются со сдвигом битов, перечисления не будут иметь произвольного числа членов, и в любом случае это не сработает для более чем 32 или 64 элементов. Не имеет значения отдаленно как вопрос производительности.
@jwg Я согласен, глупо чрезмерно беспокоиться о производительности этого во время выполнения, но все же я думаю, что приятно знать, что это не будет вставлять битовые сдвиги везде, где вы используете перечисление. Больше похоже на то, что это изящно, а не на производительность.
@JeremyWeir - В значении перечисления флагов будет установлено несколько битов. Значит, ваш метод анализа данных неправильный. Запустите процедуру для представления вашего целочисленного значения в двоичном формате. 131,072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. При установленном 17-м бите значение перечисления присвоено 1
Также отметим, что вы можете сдвигать бит от «предыдущих» значений перечисления, а не напрямую от чисел, то есть Third = Second << 1, а не Third = 1 << 2 - см. более полное описание ниже
Мне это нравится, но как только вы дойдете до верхних пределов базового типа перечисления, компилятор не предупредит о битовых сдвигах, таких как 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2 и т. д. Напротив, если вы скажете ThirtySecond = 2147483648 для перечисления типа int, компилятор выдаст ошибку.
Просто чтобы добавить еще один вариант, новый в C# 7, вы можете использовать 0b1, 0b10 и т. д. (Или 0b00000001, если вам нравится, что все выстраивается правильно!)
Атрибут [Flags] следует использовать всякий раз, когда перечисляемое представляет собой набор возможных значений, а не одно значение. Такие коллекции часто используются с поразрядными операторами, например:
var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;
Обратите внимание, что атрибут [Flags]не включает это сам по себе - все, что он делает, это позволяет красивое представление методом .ToString():
enum Suits { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }
[Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }
...
var str1 = (Suits.Spades | Suits.Diamonds).ToString();
// "5"
var str2 = (SuitsFlags.Spades | SuitsFlags.Diamonds).ToString();
// "Spades, Diamonds"
Также важно отметить, что [Flags]не автоматически делает значения перечисления степенями двойки. Если вы опустите числовые значения, перечисление не будет работать, как можно было бы ожидать от побитовых операций, потому что по умолчанию значения начинаются с 0 и увеличиваются.
Неправильная декларация:
[Flags]
public enum MyColors
{
Yellow, // 0
Green, // 1
Red, // 2
Blue // 3
}
Значения, если они объявлены таким образом, будут: Желтый = 0, Зеленый = 1, Красный = 2, Синий = 3. Это сделает его бесполезным в качестве флагов.
Вот пример правильного объявления:
[Flags]
public enum MyColors
{
Yellow = 1,
Green = 2,
Red = 4,
Blue = 8
}
Чтобы получить отдельные значения в вашем свойстве, можно сделать следующее:
if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
// Yellow is allowed...
}
или до .NET 4:
if ((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
// Yellow is allowed...
}
if ((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
// Green is allowed...
}
Под одеялом
Это работает, потому что вы использовали степень двойки в своем перечислении. Под обложками ваши значения перечисления выглядят так в двоичных единицах и нулях:
Yellow: 00000001
Green: 00000010
Red: 00000100
Blue: 00001000
Точно так же после того, как вы установили для своего свойства AllowedColors значение Red, Green и Blue с помощью двоичного побитового оператора OR |, AllowedColors будет выглядеть следующим образом:
myProperties.AllowedColors: 00001110
Итак, когда вы извлекаете значение, вы фактически выполняете побитовое И & для значений:
myProperties.AllowedColors: 00001110
MyColor.Green: 00000010
-----------------------
00000010 // Hey, this is the same as MyColor.Green!
Значение None = 0
А что касается использования 0 в вашем перечислении, цитируя MSDN:
[Flags]
public enum MyColors
{
None = 0,
....
}
Use None as the name of the flag enumerated constant whose value is zero. You cannot use the None enumerated constant in a bitwise AND operation to test for a flag because the result is always zero. However, you can perform a logical, not a bitwise, comparison between the numeric value and the None enumerated constant to determine whether any bits in the numeric value are set.
Вы можете найти больше информации об атрибуте flags и его использовании в msdn и разработка флагов в msdn
Кроме того, я не понимаю, что на самом деле делает атрибут Flags? Кажется, я могу определить и использовать перечисление флагов без атрибута. Используется ли это просто как указание на то, что перечисление можно использовать как флаги?
Сами по себе флаги ничего не делают. Кроме того, C# не требует наличия флагов как таковых. Но реализация ToString вашего перечисления использует флаги, как и Enum.IsDefined, Enum.Parse и т. д. Попробуйте удалить флаги и посмотрите на результат MyColor.Yellow | MyColor.Red; без него вы получите «5», с флажками вы получите «желтый, красный». Некоторые другие части платформы также используют [Flags] (например, сериализацию XML).
Атрибут Flags оказывает большее влияние, чем метод ToString. Visual studio использует его, по крайней мере, до некоторой степени при использовании Enum в дизайнере. Смотрите мой вопрос здесь: stackoverflow.com/q/15663166/1111886
Я предпочитаю использовать константы вида A = 1
Метод .HasFlag (Enum flag) принимает параметр типа Enum, поэтому тип значения должен быть упакован, что плохо для производительности
@AlekseiChepovoi Enum является производным от класса ValueType, это означает, что он обрабатывается как struct так же, как int, float и т. д.
@borrrden, когда мы передаем класс Enum (класс, который определяет метод HasFlag), он передается по ссылке, как я понял. Я читал об этом в третьей редакции CLR Джеффри Рихтера через C#, в главе «Перечисления и битовые флаги».
@AlekseiChepovoi У меня тоже есть эта книга, я проверю ее. Однако, похоже, это противоречит всему, что я знаю о C#. В каждой статье, которую я могу найти, говорится, что перечисления - это объекты значений, которые не размещаются в куче.
@borrrden, черт возьми! Я нашел это: msdn.microsoft.com/en-us/library/system.enum.aspx - см. Часть «Примечания»: «Enum - это базовый класс для всех перечислений в .NET Framework». и «Перечисление не наследуется явно от Enum; отношение наследования неявно обрабатывается компилятором». Итак, когда вы пишете: public enum bla bla bla - это тип значения. Но метод HasFlag хочет, чтобы вы дали ему экземпляр System.Enum, который является классом (ссылочный тип :)
Enum.IsDefined не принимает во внимание атрибут FlagsAttribute. Ни одно из следующих значений не возвращает истину, даже с атрибутом: Желтый | Зеленый, «Желтый, Зеленый», 3
ваш пример будет иметь больше смысла с точки зрения восприятия флага, если синий = 2 и зеленый = 3, поскольку, в конце концов, зеленый - это комбинация желтого и синего
Если вы хотите исключить флаг из перечисления, используйте xor, который в C# равен ^. Так что если у вас myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Вы можете сделать: myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Чтобы добавить Mode.Write:
Mode = Mode | Mode.Write;
@Nidonocu
Чтобы добавить еще один флаг к существующему набору значений, используйте оператор присваивания OR.
Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
&& ((Mode & Mode.Read) == Mode.Read)));
Я спросил недавно о чем-то подобном.
Если вы используете флаги, вы можете добавить метод расширения в перечисления, чтобы упростить проверку содержащихся флагов (подробности см. В сообщении)
Это позволяет делать:
[Flags]
public enum PossibleOptions : byte
{
None = 0,
OptionOne = 1,
OptionTwo = 2,
OptionThree = 4,
OptionFour = 8,
//combinations can be in the enum too
OptionOneAndTwo = OptionOne | OptionTwo,
OptionOneTwoAndThree = OptionOne | OptionTwo | OptionThree,
...
}
Тогда вы сможете:
PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree
if ( opt.IsSet( PossibleOptions.OptionOne ) ) {
//optionOne is one of those set
}
Я считаю, что это легче читать, чем большинство способов проверки включенных флагов.
Я полагаю, что IsSet - это метод расширения?
Да - прочтите другой вопрос, на который я ссылаюсь, чтобы узнать подробности: stackoverflow.com/questions/7244
.NET 4 добавляет метод HasFlag в перечисления, поэтому вы можете использовать opt.HasFlag( PossibleOptions.OptionOne ) без необходимости писать свои собственные расширения.
Обратите внимание, что HasFlag является намного медленнее, чем выполнение побитовых операций.
@WaiHaLee Вы можете использовать метод расширения CodeJam.EnumHelper.IsFlagSet, который скомпилирован в быструю версию с использованием выражения: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet_ _1
Для меня в конструкции if ((x & y) == y)... есть что-то излишне многословное, особенно если x и y являются составными наборами флагов, и вам нужно только знать, есть ли перекрытие любой.
В этом случае все, что вам действительно нужно знать, это если после битовой маски есть ненулевое значение [1].
[1] See Jaime's comment. If we were authentically bitmasking, we'd only need to check that the result was positive. But since
enums can be negative, even, strangely, when combined with the[Flags]attribute, it's defensive to code for!= 0rather than> 0.
Построение установки @andnil ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BitFlagPlay
{
class Program
{
[Flags]
public enum MyColor
{
Yellow = 0x01,
Green = 0x02,
Red = 0x04,
Blue = 0x08
}
static void Main(string[] args)
{
var myColor = MyColor.Yellow | MyColor.Blue;
var acceptableColors = MyColor.Yellow | MyColor.Red;
Console.WriteLine((myColor & MyColor.Blue) != 0); // True
Console.WriteLine((myColor & MyColor.Red) != 0); // False
Console.WriteLine((myColor & acceptableColors) != 0); // True
// ... though only Yellow is shared.
Console.WriteLine((myColor & MyColor.Green) != 0); // Wait a minute... ;^D
Console.Read();
}
}
}
Enum может быть основан на типе со знаком, поэтому вы должны использовать «! = 0» вместо «> 0».
@JaimePardos - пока мы храним их честные байты, как я это делаю в этом примере, нет понятия отрицательного. Просто от 0 до 255. Как и в случае MSDN предупреждает: «Будьте осторожны, если вы определяете отрицательное число как константу, перечисляемую флагом, потому что ... [это] может запутать ваш код и способствовать ошибкам кодирования». Странно думать о «отрицательных битовых флагах»! ; ^) Я немного отредактирую. Но вы правы, если мы будем использовать отрицательные значения в нашем enum, нам нужно будет проверить != 0.
Комбинируя ответы https://stackoverflow.com/a/8462/1037948 (объявление с помощью битового сдвига) и https://stackoverflow.com/a/9117/1037948 (с использованием комбинаций в объявлении), вы можете сдвигать предыдущие значения по битам, а не использовать числа. Не обязательно рекомендовать это, но просто указать, что вы можете.
Скорее, чем:
[Flags]
public enum Options : byte
{
None = 0,
One = 1 << 0, // 1
Two = 1 << 1, // 2
Three = 1 << 2, // 4
Four = 1 << 3, // 8
// combinations
OneAndTwo = One | Two,
OneTwoAndThree = One | Two | Three,
}
Вы можете объявить
[Flags]
public enum Options : byte
{
None = 0,
One = 1 << 0, // 1
// now that value 1 is available, start shifting from there
Two = One << 1, // 2
Three = Two << 1, // 4
Four = Three << 1, // 8
// same combinations
OneAndTwo = One | Two,
OneTwoAndThree = One | Two | Three,
}
Подтверждение с помощью LinqPad:
foreach(var e in Enum.GetValues(typeof(Options))) {
string.Format("{0} = {1}", e.ToString(), (byte)e).Dump();
}
Результаты в:
None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
Комбинации - хорошая рекомендация, но я думаю, что связанный битовый сдвиг будет подвержен ошибкам копирования и вставки, таким как Два = Один.
@RupertRawnsley, чтобы процитировать мой ответ:> Не обязательно рекомендовать это, но просто указываю, что вы можете
В дополнение к принятому ответу в C# 7 флаги перечисления могут быть записаны с использованием двоичных литералов:
[Flags]
public enum MyColors
{
None = 0b0000,
Yellow = 0b0001,
Green = 0b0010,
Red = 0b0100,
Blue = 0b1000
}
Я думаю, это представление дает понять, как работают флаги под одеялом.
А в C# 7.2 с ведущий разделитель еще яснее! 0b_0100
При работе с флагами я часто объявляю дополнительные элементы None и All. Они помогают проверить, установлены ли все флаги или нет.
[Flags]
enum SuitsFlags {
None = 0,
Spades = 1 << 0,
Clubs = 1 << 1,
Diamonds = 1 << 2,
Hearts = 1 << 3,
All = ~(~0 << 4)
}
Использование:
Spades | Clubs | Diamonds | Hearts == All // true
Spades & Clubs == None // true
Обновление 2019-10:
Начиная с C# 7.0 вы можете использовать двоичные литералы, которые, вероятно, более интуитивно понятны для чтения:
[Flags]
enum SuitsFlags {
None = 0b0000,
Spades = 0b0001,
Clubs = 0b0010,
Diamonds = 0b0100,
Hearts = 0b1000,
All = 0b1111
}
Да, объединив все ответы на этой странице, я сейчас регулярно использую этот, но с ведущим разделителем 0b_1111
Приносим извинения, если кто-то уже заметил этот сценарий. Прекрасный пример флагов, которые мы можем увидеть в отражении. Да Флаги привязки ENUM.
[System.Flags]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public enum BindingFlags
использование
// BindingFlags.InvokeMethod
// Call a static method.
Type t = typeof (TestClass);
Console.WriteLine();
Console.WriteLine("Invoking a static method.");
Console.WriteLine("-------------------------");
t.InvokeMember ("SayHello", BindingFlags.InvokeMethod | BindingFlags.Public |
BindingFlags.Static, null, null, new object [] {});
Флаги используются, когда перечислимое значение представляет собой коллекцию члены перечисления.
здесь мы используем поразрядные операторы, | и &
Пример
[Flags]
public enum Sides { Left=0, Right=1, Top=2, Bottom=3 }
Sides leftRight = Sides.Left | Sides.Right;
Console.WriteLine (leftRight);//Left, Right
string stringValue = leftRight.ToString();
Console.WriteLine (stringValue);//Left, Right
Sides s = Sides.Left;
s |= Sides.Right;
Console.WriteLine (s);//Left, Right
s ^= Sides.Right; // Toggles Sides.Right
Console.WriteLine (s); //Left
Ваш пример неверен (и не дает заявленного вывода), потому что для правильного использования Flags требуется, чтобы в ваших значениях перечисления были установлены уникальные биты. И должно быть установлено как минимум один (если вы не используете псевдо-значение, например None). Ваше перечисление должно иметь вид `public enum Sides {Left = 1, Right = 2, Top = 4, Bottom = 8}`
Также стоит отметить, в дополнение к принятому ответу, что VB.NET на самом деле требует [Flags] - по крайней мере, по мнению ребят из .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread /…