Типы перечислений (справочник по C#)
Тип перечисления (или тип enum) — это тип значения, определенный набором именованных констант применяемого целочисленного типа. Чтобы определить тип перечисления, используйте ключевое слово enum и укажите имена элементов перечисления:
Вы не можете определить метод внутри определения типа перечисления. Чтобы добавить функциональные возможности к типу перечисления, создайте метод расширения.
Тип перечисления используется для представления выбора из набора взаимоисключающих значений или комбинации вариантов выбора. Чтобы представить комбинацию вариантов выбора, определите тип перечисления как битовые флаги.
Типы перечислений как битовые флаги
Дополнительные сведения и примеры см. на странице справочника по API System.FlagsAttribute и в разделе о неисключительных элементах и атрибутах Flags страницы справочника по API System.Enum.
Тип System.Enum и ограничение перечисления
Тип System.Enum является абстрактным базовым классом всех типов перечисления. Он предоставляет различные методы, позволяющие получить информацию о типе перечисления и его значениях. Дополнительные сведения и примеры см. на странице справочника по API System.Enum.
Преобразования
Для любого типа перечисления существуют явные преобразования между типом перечисления и его базовым целочисленным типом. Если вы привели значение перечисления к его базовому типу, то результатом будет связанное целочисленное значение элемента перечисления.
Используйте метод Enum.IsDefined, чтобы определить, содержит ли тип перечисления элемент перечисления с определенным связанным значением.
Для любого типа перечисления существует упаковка — преобразование и распаковка — преобразование в тип System.Enum и из него соответственно.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
Перечисления (C++)
Перечисление — это пользовательский тип, состоящий из набора целочисленных констант, называемых перечислителями.
В этой статье рассматривается тип языка C++ Standard в формате ISO enum и тип enum с областью действия (или строго типизированный), который появился в C++ 11. Сведения об открытых классах enum или закрытых типах классов enum в c++/CLI и c++/CX см. в разделе Класс Enum.
Синтаксис
Параметры
identifier
Имя типа, присваиваемое перечислению.
type
Базовый тип перечислителей; все перечислители имеют один базовый тип. Может быть любым целочисленным типом.
Область перечислителя
Перечисление предоставляет контекст для описания диапазона значений, которые представлены в виде именованных констант и также называются перечислителями. В первоначальных типах перечислений C и C++ перечислители с неполным имеем являются видимыми внутри области видимости, в которой объявлено перечисление. В ограниченных перечислениях имя перечислителя должно уточняться именем типа перечисления. В следующем примере демонстрируется основное различие между двумя видами перечислений.
Каждому имени в перечислении присваивается целочисленное значение, которое соответствует определенному месту в порядке значений в перечислении. По умолчанию первому значению присваивается 0, следующему — 1 и т. д., но можно задать значение перечислителя явным образом, как показано ниже:
Каждый перечислитель рассматривается как константа и должен иметь уникальное имя в пределах области, где enum определено (для перечислений с неограниченным диапазоном) или внутри enum самого себя (для перечислений с областью видимости). Значения, задаваемые имена, могут быть неуникальными. Например, для следующего объявления неограниченного перечисления Suit :
Приведение правил
Неограниченные константы перечисления могут быть неявно преобразованы в, но никогда не может быть неявно преобразовано int int в значение enum. В следующем примере показано, что пройдет при попытке присвоить переменной hand значение, не относящееся к типу Suit :
Для преобразования int в перечислитель с областью или вне области действия требуется приведение. Однако неограниченный перечислитель можно преобразовать в целочисленное значение без приведения.
Использование подобных неявных преобразований может приводить к непредвиденным побочным эффектам. Чтобы избежать ошибок программирования, связанных с неограниченными перечислениями, значения ограниченных перечислений являются строго типизированными. Ограниченные перечислители должны уточняться именем типа перечисления (идентификатором); они не могут быть неявно преобразованы, как показано в следующем примере:
Обратите внимание, что в строке hand = account_num; по-прежнему содержится ошибка, которая происходит при использовании неограниченных перечислений, как показано выше. Эта ошибка устраняется с помощью явного приведения. Однако при использовании ограниченных перечислений попытка преобразования в следующем операторе — account_num = Suit::Hearts; — больше не будет разрешена без явного приведения.
Перечисления без перечислителей
Visual Studio 2017 версии 15,3 и более поздних версий (доступно в и более поздних версиях): путем определения перечисления (regular или с ограниченной областью) с явным базовым типом и без перечислителей, вы можете использовать новый целочисленный тип, который не имеет неявного преобразования в любой другой тип. Используя этот тип вместо встроенного базового типа, можно исключить потенциальные ошибки для незначительных ошибок, вызванных случайными неявными преобразованиями.
Новый тип является точной копией базового типа и, таким образом, имеет то же соглашение о вызовах, что означает, что его можно использовать в ABI без снижения производительности. Если переменные типа инициализируются с помощью инициализации прямого списка, приведение не требуется. В следующем примере показано, как инициализировать перечисления без перечислителей в различных контекстах:
Типы struct, union и enum в Modern C++
Язык C++ сильно изменился за последние 10 лет. Изменились даже базовые типы: struct, union и enum. Сегодня мы кратко пройдёмся по всем изменениям от C++11 до C++17, заглянем в C++20 и в конце составим список правил хорошего стиля.
Зачем нужен тип struct
Тип struct — фундаментальный. Согласно C++ Code Guidelines, struct лучше использовать для хранения значений, не связанных инвариантом. Яркие примеры — RGBA-цвет, вектора из 2, 3, 4 элементов или информация о книге (название, количество страниц, автор, год издания и т.п.).
Он похож на class, но есть два мелких различия:
Согласно C++ Core Guidelines, struct хорошо применять для сокращения числа параметров функции. Этот приём рефакторинга известен как «parameter object».
Кроме того, структуры могут сделать код более лаконичным. Например, в 2D и 3D графике удобнее считать в 2-х и 3-х компонентных векторах, чем в числах. Ниже показан код, использующий библиотеку GLM (OpenGL Mathematics)
Эволюция struct
В C++11 появилась инициализация полей при объявлении.
Ранее для таких целей приходилось писать свой конструктор:
Вместе с инициализацией при объявлении пришла проблема: мы не можем использовать литерал структуры, если она использует инициализацию полей при объявлении:
В C++11 и C++14 это решалось вручную написанием конструктора с boilerplate кодом. В C++17 ничего дописывать не надо — стандарт явно разрешает агрегатную инициализацию для структур с инициализаторами полей.
В примере написаны конструкторы, необходимые только в C++11 и C++14:
В C++20 агрегатная инициализация обещает стать ещё лучше! Чтобы понять проблему, взгляните на пример ниже и назовите каждое из пяти инициализируемых полей. Не перепутан ли порядок инициализации? Что если кто-то в ходе рефакторинга поменяет местами поля в объявлении структуры?
В C11 появилась удобная возможность указать имена полей при инициализации структуры. Эту возможность обещают включить в C++20 под названием «назначенный инициализатор» («designated initializer»). Подробнее об этом в статье Дорога к С++20.
В C++17 появился structured binding, также известный как «декомпозиция при
объявлении». Этот механизм работает со структурами, с std::pair и std::tuple и дополняет агрегатную инициализацию.
В сочетании с классами STL эта фишка может сделать код элегантнее:
Зачем нужен тип union
Вообще-то в C++17 он не нужен в повседневном коде. C++ Core Guidelines предлагают строить код по принципу статической типобезопасности, что позволяет компилятору выдать ошибку при откровенно некорректной обработке данных. Используйте std::variant как безопасную замену union.
Если же вспоминать историю, union позволяет переиспользовать одну и ту же область памяти для хранения разных полей данных. Тип union часто используют в мультимедийных библиотеках. В них разыгрывается вторая фишка union: идентификаторы полей анонимного union попадают во внешнюю область видимости.
Эволюция union
В C++11 вы можете складывать в union типы данных, имеющие собственные конструкторы. Вы можете объявить свой констуктор union. Однако, наличие конструктора ещё не означает корректную инициализацию: в примере ниже поле типа std::string забито нулями и вполне может быть невалидным сразу после конструирования union (на деле это зависит от реализации STL).
В C++17 код мог бы выглядеть иначе, используя variant. Внутри variant использует небезопасные конструкции, которые мало чем отличаются от union, но этот опасный код скрыт внутри сверхнадёжной, хорошо отлаженной и протестированной STL.
Зачем нужен тип enum
Тип enum хорошо использовать везде, где есть состояния. Увы, многие программисты не видят состояний в логике программы и не догадываются применить enum.
Ниже пример кода, где вместо enum используют логически связанные булевы поля. Как думаете, будет ли класс работать корректно, если m_threadShutdown окажется равным true, а m_threadInitialized — false?
Другой пример — магические числа, без которых якобы никак. Пусть у вас есть галерея 4 слайдов, и программист решил захардкодить генерацию контента этих слайдов, чтобы не писать свой фреймворк для галерей слайдов. Появился такой код:
Даже если хардкод слайдов оправдан, ничто не может оправдать магические числа. Их легко заменить на enum, и это по крайней мере повысит читаемость.
Иногда enum используют как набор флагов. Это порождает не очень наглядный код:
Возможно, вам лучше использовать std::bitset :
Иногда программисты записывают константы в виде макросов. Такие макросы легко заменить на enum или constexpr.
Эволюция enum
Кроме того, для enum и scoped enum появилась возможность явно выбрать тип, используемый для представления перечисления в сгенерированном компилятором коде:
В некоторых новых языках, таких как Swift или Rust, тип enum по умолчанию является строгим в преобразованиях типов, а константы вложены в область видимости типа enum. Кроме того, поля enum могут нести дополнительные данные, как в примере ниже
Правила хорошего стиля
Подведём итоги в виде списка правил:
Из таких мелочей строится красота и лаконичность кода в телах функций. Лаконичные функции легко рецензировать на Code Review и легко сопровождать. Из них строятся хорошие классы, а затем и хорошие программные модули. В итоге программисты становятся счастливыми, на их лицах расцветают улыбки.
Класс Enum (C++/CLI и C++/CX)
Объявляет перечисление в области видимости пространства имен, которое является определяемым пользователем типом, состоящим из ряда именованных констант, называемых перечислителями.
Все среды выполнения
Примечания
Среда выполнения Windows
Синтаксис
Параметры
enumeration-identifier
Имя перечисления.
базовый тип
(Необязательно) Базовый тип перечисления.
Перечислитель-список
Разделенный запятыми список имен перечислителей.
Значение каждого перечислителя — константное выражение, которое или определяется неявно компилятором, или явно нотацией enumerator constant-expression. По умолчанию значение первого перечислителя ноль, если он определен неявно. Значение каждого следующего неявно определенного перечислителя — значение предыдущего перечислителя + 1.
var
(Необязательно) Имя переменной типа перечисления.
Примечания
Дополнительные сведения и примеры см. в разделе Перечисления.
Обратите внимание, что компилятор выводит сообщения об ошибках, если константное выражение, задающее значение перечислителя, не может быть представлено underlying-type. Однако компилятор не сообщает об ошибке для значения, недопустимого для базового типа. Пример.
Если базовый тип является числовым, а перечислитель задает максимальное значение для этого типа, то значение следующего неявно определенного перечисления не может быть представлено.
Если underlying-type является и более двух перечислителей определены неявно, то нельзя представить перечислители после первых двух.
Если underlying-type является и значение перечисления в диапазоне от 0xD800 до 0xDFFF, то значение можно представить. Однако логически значение неверно, так как оно представляет половину пары символов-заместителей Юникода и не должно отображаться в изоляции.
Требования
Параметр компилятора: /ZW
Среда CLR
Синтаксис
Параметры
Перечислитель-список
Разделенный запятыми список идентификаторов (перечислителей) в перечислении.
name
Имя перечисления. Анонимные управляемые перечисления не допускаются.
var
(Необязательно) Имя переменной типа перечисления.
Примечания
enum class и enum struct являются эквивалентными объявлениями.
Существуют два типа перечислений: управляемые (C++/CX) и стандартные.
Управляемые перечисления (или перечисления C++/CX) могут быть определены следующим образом:
Это семантически эквивалентно:
Стандартное перечисление может быть определено следующим образом:
Это семантически эквивалентно:
Управляемые имена перечислителей (identifiers) не вводятся в область, в которой определяется перечисление; все ссылки на перечислители должны быть полными (имя идентификатор). По этой причине нельзя определить анонимное управляемое перечисление.
Перечислители стандартного перечисления строго вводятся во внешнюю область. То есть, если есть другой символ с таким же именем, как у перечислителя во внешней области видимости, компилятор выдаст ошибку.
В Visual Studio 2002 и Visual Studio 2003 перечислители были нестрого вводимыми (видимыми во внешней области, если не было другого идентификатора с таким же именем).
Если определено стандартное перечисление C++ (без class или struct ), компиляция с помощью /clr приведет к компиляции перечисления в качестве управляемого перечисления. Перечисление по-прежнему имеет семантику неуправляемого перечисления. Обратите внимание, что компилятор вводит атрибут Microsoft::VisualC::NativeEnumAttribute для определения намерения программиста сделать перечисление собственным. Другие компиляторы просто увидят стандартное перечисление как управляемое перечисление.
В Visual Studio 2002 и Visual Studio 2003 стандартное перечисление используется в качестве типа в параметре функции.
Этот код выдал бы в MSIL для сигнатуры функции следующее:
Однако в текущих версиях компилятора стандартное перечисление выдается как управляемое перечисление с [NativeEnumAttribute], а в MSIL для сигнатуры функции выдается следующее:
Дополнительные сведения о неуправляемых перечислителях см. в разделе Объявление перечислений C++.
Дополнительные сведения о перечислителях CLR см. в следующем разделе:
Урок №59. Классы enum
Хотя перечисления и считаются отдельными типами данных в языке C++, они не столь безопасны, как кажутся на первый взгляд, и в некоторых случаях позволят вам делать вещи, которые не имеют смысла. Например:
Для решения этой проблемы в C++11 добавили классы enum (или «перечисления с областью видимости»), которые добавляют перечислениям, как вы уже могли понять, локальную область видимости со всеми её правилами. Для создания такого класса нужно просто добавить ключевое слово class сразу после enum. Например:
Стандартные перечислители находятся в той же области видимости, что и само перечисление (в глобальной области видимости), поэтому вы можете напрямую получить к ним доступ (например, PINK ). Однако с добавлением класса, который ограничивает область видимости каждого перечислителя областью видимости его перечисления, для доступа к нему потребуется оператор разрешения области видимости (например, Colors::PINK ). Это значительно снижает риск возникновения конфликтов имен.
А строгие правила типов классов enum означают, что каждый класс enum считается уникальным типом. Это означает, что компилятор не сможет сравнивать перечислители из разных перечислений. Если вы попытаетесь это сделать, компилятор выдаст ошибку (как в примере, приведенном выше).
Однако учтите, что вы можете сравнивать перечислители внутри одного класса enum (так как эти перечислители принадлежат одному типу):
С классами enum компилятор больше не сможет неявно конвертировать значения перечислителей в целые числа. Это хорошо! Но иногда могут быть ситуации, когда нужно будет вернуть эту особенность. В таких случаях вы можете явно преобразовать перечислитель класса enum в тип int, используя оператор static_cast:
Если вы используете компилятор, поддерживающий C++11, то нет никакого смысла использовать обычные перечисления вместо классов enum.
Обратите внимание, ключевое слово class вместе с ключевым словом static являются одними из самых запутанных в языке C++, поскольку результат сильно зависит от варианта применения (контекста). Хотя классы enum используют ключевое слово class, в C++ они не считаются традиционными «классами». О традиционных классах мы поговорим несколько позже.
Поделиться в социальных сетях:
Урок №58. Перечисления
Комментариев: 17
Интереса ради придумал пример с перечислениями.
может кому то будет интересно.
Класс! Я когда-то давно с++ изучал. Теперь понадобилось и когда увидел enum class у меня аж ступор был — почему оно работает как-то не так ))) Теперь всё стало понятно.
Между прочим в C# гораздо удобнее enum. Там даже можно извлекать названия полей в виде строк… А этот enum оч неудобен! И перевод его в разряд class делает его ещё более неудобным чем без… Почему вообще это придумали? И чем они вообще руководствовались? Если уж делать, то делать так чтобы было лучше, а не добавлять проблемы… Или по крайней мере так, чтобы их можно было удобно решить.
Использование enum class скорей вызывает массу проблем, чем удобства. Потому что да, можно обращаться через четырёхточие как элемент класса. Но только тут это удобно. Однако во всех остальных местах прут неудобства со всех щелей… Приходится писать кучу никчемных операторов просто для разных операций, коньюнкции побитовой, дизьюнкции побитовой, равенства неравенства и тд и тп… А если к несчастью вам надо извлечь данные в виде типа unsigned, то оказывается, это невозможно. Потому что надо добавить оператор преобразования типа в сам класс enum. По синтаксису это недопустимо ибо оператор приведения типа не мб внешним…. Тупик! Приходится писать явное преобразование типа… даже если вы напишете
enum EType class:unsigned
Жёстко, раскидал по фактам действительно недо-класс получился



