Что означает атрибут перечисления [Flags] в C#?

Время от времени я вижу следующее перечисление:

[Flags]
public enum Options 
{
    None    = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}

Я не понимаю, что именно делает атрибут [Flags].

У кого-нибудь есть хорошее объяснение или пример, который они могли бы опубликовать?

Также стоит отметить, в дополнение к принятому ответу, что VB.NET на самом деле требует [Flags] - по крайней мере, по мнению ребят из .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread‌ /…

Rushyo 30.07.2012 19:38

Обратите внимание, в настоящее время в VB не требуется. Сохранить поведение как C# - просто изменяет вывод ToString (). Обратите внимание: вы также можете выполнить логическое ИЛИ ВНУТРИ самого Enum. Очень круто. Кот = 1, Собака = 2, CatAndDog = Кот || Собака.

Chalky 04.08.2015 12:13

@Chalky Вы имеете в виду CatAndDog = Cat | Dog (логическое ИЛИ вместо условного), я полагаю?

DdW 12.09.2016 15:20

@DdW, частично верно: | следует использовать, но | называется двоичным ИЛИ. II - это логическое ИЛИ (которое допускает короткое замыкание): по крайней мере, согласно Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx

Pieter21 01.04.2018 09:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1 528
4
509 152
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Флаги позволяют вам использовать битовую маску внутри вашего перечисления. Это позволяет комбинировать значения перечисления, сохраняя при этом те, которые указаны.

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}

Это неверно, вы можете использовать битовую маску, даже если перечисление не помечено как Flags.

Shadow 01.11.2020 21:56

См. Следующий пример, демонстрирующий декларацию и возможное использование:

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

Этот пример работает, даже если вы не укажете [Флаги]. Это часть [Флаги], о которой я пытаюсь узнать.

gbarry 07.08.2019 02:21

Вы также можете сделать это

[Flags]
public enum MyEnum
{
    None   = 0,
    First  = 1 << 0,
    Second = 1 << 1,
    Third  = 1 << 2,
    Fourth = 1 << 3
}

Я считаю, что сдвиг битов проще, чем набирать 4,8,16,32 и так далее. Это не влияет на ваш код, потому что все это делается во время компиляции.

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

JeremyWeir 14.05.2012 10:15

@JeremyWeir Можете ли вы объяснить, чем это будет отличаться от использования 1, 2 , 4, 8, 16, ...? Также в калькуляторе Windows 7 есть биты, на которые вы можете нажимать.

Silvermind 27.05.2012 12:03

Вот что я имею в виду, я предпочитаю иметь полное int в исходном коде. Если у меня есть столбец в таблице в базе данных с именем MyEnum, в котором хранится значение одного из перечислений, а запись содержит 131072, мне нужно будет достать свой калькулятор, чтобы выяснить, что соответствует перечислению со значением 1

JeremyWeir 29.05.2012 21:11

«Все это делается во время компиляции», этот тип комментария может быть интересен некоторым, но я нахожу его разочаровывающим, потому что он подразумевает, что, скажем, 20 битовых сдвигов во время выполнения будут иметь какое-либо измеримое влияние на производительность. Процессоры очень хорошо справляются со сдвигом битов, перечисления не будут иметь произвольного числа членов, и в любом случае это не сработает для более чем 32 или 64 элементов. Не имеет значения отдаленно как вопрос производительности.

jwg 14.02.2013 13:48

@jwg Я согласен, глупо чрезмерно беспокоиться о производительности этого во время выполнения, но все же я думаю, что приятно знать, что это не будет вставлять битовые сдвиги везде, где вы используете перечисление. Больше похоже на то, что это изящно, а не на производительность.

Orion Edwards 15.02.2013 23:25

@JeremyWeir - В значении перечисления флагов будет установлено несколько битов. Значит, ваш метод анализа данных неправильный. Запустите процедуру для представления вашего целочисленного значения в двоичном формате. 131,072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. При установленном 17-м бите значение перечисления присвоено 1

Brett Caswell 19.03.2013 09:41

Также отметим, что вы можете сдвигать бит от «предыдущих» значений перечисления, а не напрямую от чисел, то есть Third = Second << 1, а не Third = 1 << 2 - см. более полное описание ниже

drzaus 16.05.2013 19:05

Мне это нравится, но как только вы дойдете до верхних пределов базового типа перечисления, компилятор не предупредит о битовых сдвигах, таких как 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2 и т. д. Напротив, если вы скажете ThirtySecond = 2147483648 для перечисления типа int, компилятор выдаст ошибку.

aaaantoine 04.06.2014 00:58

Просто чтобы добавить еще один вариант, новый в C# 7, вы можете использовать 0b1, 0b10 и т. д. (Или 0b00000001, если вам нравится, что все выстраивается правильно!)

Graham Wager 15.03.2017 20:43
Ответ принят как подходящий

Атрибут [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? Кажется, я могу определить и использовать перечисление флагов без атрибута. Используется ли это просто как указание на то, что перечисление можно использовать как флаги?

Oskar 06.08.2009 13:11

Сами по себе флаги ничего не делают. Кроме того, C# не требует наличия флагов как таковых. Но реализация ToString вашего перечисления использует флаги, как и Enum.IsDefined, Enum.Parse и т. д. Попробуйте удалить флаги и посмотрите на результат MyColor.Yellow | MyColor.Red; без него вы получите «5», с флажками вы получите «желтый, красный». Некоторые другие части платформы также используют [Flags] (например, сериализацию XML).

Ruben 17.08.2009 21:30

Атрибут Flags оказывает большее влияние, чем метод ToString. Visual studio использует его, по крайней мере, до некоторой степени при использовании Enum в дизайнере. Смотрите мой вопрос здесь: stackoverflow.com/q/15663166/1111886

Fr33dan 27.03.2013 19:50

Я предпочитаю использовать константы вида A = 1

Nick Westgate 09.04.2013 16:06

Метод .HasFlag (Enum flag) принимает параметр типа Enum, поэтому тип значения должен быть упакован, что плохо для производительности

Aleksei Chepovoi 04.05.2013 16:59

@AlekseiChepovoi Enum является производным от класса ValueType, это означает, что он обрабатывается как struct так же, как int, float и т. д.

borrrden 24.06.2013 10:46

@borrrden, когда мы передаем класс Enum (класс, который определяет метод HasFlag), он передается по ссылке, как я понял. Я читал об этом в третьей редакции CLR Джеффри Рихтера через C#, в главе «Перечисления и битовые флаги».

Aleksei Chepovoi 27.06.2013 00:45

@AlekseiChepovoi У меня тоже есть эта книга, я проверю ее. Однако, похоже, это противоречит всему, что я знаю о C#. В каждой статье, которую я могу найти, говорится, что перечисления - это объекты значений, которые не размещаются в куче.

borrrden 27.06.2013 05:35

@borrrden, черт возьми! Я нашел это: msdn.microsoft.com/en-us/library/system.enum.aspx - см. Часть «Примечания»: «Enum - это базовый класс для всех перечислений в .NET Framework». и «Перечисление не наследуется явно от Enum; отношение наследования неявно обрабатывается компилятором». Итак, когда вы пишете: public enum bla bla bla - это тип значения. Но метод HasFlag хочет, чтобы вы дали ему экземпляр System.Enum, который является классом (ссылочный тип :)

Aleksei Chepovoi 28.06.2013 18:59

Enum.IsDefined не принимает во внимание атрибут FlagsAttribute. Ни одно из следующих значений не возвращает истину, даже с атрибутом: Желтый | Зеленый, «Желтый, Зеленый», 3

TheXenocide 07.12.2013 01:29

ваш пример будет иметь больше смысла с точки зрения восприятия флага, если синий = 2 и зеленый = 3, поскольку, в конце концов, зеленый - это комбинация желтого и синего

MikeT 11.06.2015 12:14

Если вы хотите исключить флаг из перечисления, используйте xor, который в C# равен ^. Так что если у вас myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Вы можете сделать: myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green

Josh Noe 10.02.2018 01:20

Чтобы добавить 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 - это метод расширения?

Robert MacLean 02.06.2009 14:09

Да - прочтите другой вопрос, на который я ссылаюсь, чтобы узнать подробности: stackoverflow.com/questions/7244

Keith 02.06.2009 16:16

.NET 4 добавляет метод HasFlag в перечисления, поэтому вы можете использовать opt.HasFlag( PossibleOptions.OptionOne ) без необходимости писать свои собственные расширения.

Orion Edwards 20.07.2010 00:53

Обратите внимание, что HasFlag является намного медленнее, чем выполнение побитовых операций.

Wai Ha Lee 12.09.2018 18:45

@WaiHaLee Вы можете использовать метод расширения CodeJam.EnumHelper.IsFlagSet, который скомпилирован в быструю версию с использованием выражения: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet_‌ _1

NN_ 29.04.2019 23:09

Для меня в конструкции 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 != 0 rather 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».

raven 16.08.2013 13:18

@JaimePardos - пока мы храним их честные байты, как я это делаю в этом примере, нет понятия отрицательного. Просто от 0 до 255. Как и в случае MSDN предупреждает: «Будьте осторожны, если вы определяете отрицательное число как константу, перечисляемую флагом, потому что ... [это] может запутать ваш код и способствовать ошибкам кодирования». Странно думать о «отрицательных битовых флагах»! ; ^) Я немного отредактирую. Но вы правы, если мы будем использовать отрицательные значения в нашем enum, нам нужно будет проверить != 0.

ruffin 16.08.2013 17:23

Комбинируя ответы 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

Комбинации - хорошая рекомендация, но я думаю, что связанный битовый сдвиг будет подвержен ошибкам копирования и вставки, таким как Два = Один.

Rupert Rawnsley 20.03.2014 17:00

@RupertRawnsley, чтобы процитировать мой ответ:> Не обязательно рекомендовать это, но просто указываю, что вы можете

drzaus 20.03.2014 20:13

В дополнение к принятому ответу в C# 7 флаги перечисления могут быть записаны с использованием двоичных литералов:

[Flags]
public enum MyColors
{
    None   = 0b0000,
    Yellow = 0b0001,
    Green  = 0b0010,
    Red    = 0b0100,
    Blue   = 0b1000
}

Я думаю, это представление дает понять, как работают флаги под одеялом.

А в C# 7.2 с ведущий разделитель еще яснее! 0b_0100

Vincenzo Petronio 26.10.2019 11:51

При работе с флагами я часто объявляю дополнительные элементы 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

Marcelo Scofano Diniz 03.08.2020 15:25

Приносим извинения, если кто-то уже заметил этот сценарий. Прекрасный пример флагов, которые мы можем увидеть в отражении. Да Флаги привязки 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}`

Jakob 18.11.2020 16:29

Другие вопросы по теме