Для чего используется enum
Перечисления в C#: как правильно использовать enum
В C# есть много крутых инструментов, которые позволяют улучшить любой код. Один из них — enum. Давайте разберёмся, что это и как с ним работать.
Списки перечисления (или enum) сокращают код и улучшают его читаемость. В этой статье мы создадим enum и научимся применять его эффективно.
Что такое enum в C#
Это список однотипных значений: цветов, состояний, способов выравнивания и так далее. Например, в C# существует встроенный список цветов:
То есть нам не нужно вручную вводить код цвета — вместо этого мы просто выбираем значение из заранее составленного списка.
В самом enum тоже не хранится код цвета. Цифра 9 на примере выше — это индекс элемента в списке. Логика изменения цвета в нашем случае примерно такая:
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Как создать enum в C#
Создайте отдельный файл и назовите его так, чтобы понять, какой это список. Например, Direction.cs:
После объявления нового enum он используется как тип данных:
Вы можете указать и какие-то собственные значения для элементов. Например, коды ответа веб-сервера:
По умолчанию тип значения — int, но он изменяется на любой другой целочисленный тип:
Как использовать enum в C#
Самый простой пример — конструкции if и switch.
Вот результат работы такой программы:
Также вы можете использовать enum вместе с полиморфизмом.
Таким образом вы получите код, который очень быстро читается. И теперь не нужно думать, какой метод использовать, — благодаря полиморфизму всё выглядит почти как человеческая речь: set item type — Food (указать тип предмета — Еда).
Другая хорошая практика — использовать enum в качестве возвращаемого типа для методов, в которых ошибка может произойти по разным причинам. Например, отправка данных на сервер.
Этот метод возвращает три сообщения в зависимости от ситуации:
Конечно, допустимо куда больше вариантов: от ошибки базы данных до превышения времени ожидания.
Как enum помогает улучшить читаемость
Представим, что у нас есть класс Item со следующими полями:
Все эти значения передаются объекту через конструктор, поэтому инициализация выглядит так:
Без enum со временем вы забудете, за что отвечает третий аргумент, и вам придётся каждый раз проверять реализацию класса, чтобы освежить память. Или вы добавите новые типы предметов, из-за чего таких аргументов ( isArmor, isPotion) станет ещё больше:
Избежать таких неприятных моментов как раз и помогает enum: создайте перечисление ItemType и передавайте в конструктор его.
С первого взгляда понятно, что здесь имеется в виду.
Гайд по использованию enum в Python
Модуль enum содержит в себе тип для перечисления значений с возможностью итерирования и сравнения. Его можно использовать для создания понятных обозначений вместо использования чисел (для которых приходится помнить, какое число что обозначает) или строк (в которых легко опечататься и не заметить).
Создание
Итерирование
При итерировании по классу вы пройдёте по атрибутам.
Цикл будет идти по элементам в том порядке, в котором они указаны при создании класса. Названия и значения никак не влияют на порядок.
Сравнение перечислений
Так как элементы перечислений не упорядочены, то они поддерживают сравнение только по названию или значению.
IntEnum
Уникальные значения в перечислениях
Элементы перечисления с одинаковыми значениями являются несколькими названиями, указывающими на один и тот же объект.
Так как by_design и closed являются синонимами для других элементов, то они не появляются как элементы в циклах. Истинным считается название, указанное первым при объявлении.
Если вы хотите, чтобы все элементы обязательно имели разные значения, то добавьте декоратор @unique перед объявлением класса.
Элементы с повторяющимися значениями будут вызывать ValueError во время интерпретации.
Другой способ создания перечислений
Иногда удобнее не хардкодить элементы перечисления, а указывать их в более удобном виде. Для этого можно передать значения в конструктор класса:
Аргумент value является названием перечисления, которое используется для создания представления элементов. Второй аргумент names принимает список названий в перечислении. Если подать одну строку, то она будет разбита по пробельным символам и запятым, а значения будут числами, начиная с 1.
Также аргумент name принимает список пар название — значение, либо аналогичный словарь.
Передача списка пар позволяет сохранить порядок элементов, аналогично случаю, где мы объявляли атрибуты.
Другие типы значений
В этом примере каждое значение является парой из числового id и списка строк, описывающих возможный переход из данного состояния.
Данный пример аналогичен предыдущему, но в нём используются словари вместо tuple для удобства.
Типы 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-Всемогущий
Вводная
Предостережение
В этой статье вы не найдете для себя новых технических знаний, откровений, а если Вы жаждете их — то смело переходите к следующей статье. Тем не менее, здесь есть чему удивляться и есть над чем подумать. Технический юмор и филосовские мысли между строк.
Не рассказанная история.
Однажды попадает разработчик в место, где решают судьбу. Время спустя, перед ним появляется образ и спрашивает:
— Кто ты?
— Я, разработчик, звать Иван, — а про себя: Во встрял то.
Голос опять:
— Хочешь туда?. Взгляд на дверь, за которой рай.
— Ага, — робко Иван.
— Чего поведаешь мне?, — спрашивает Голос.
Немного подумав, Иван начинает говорить:
— Есть в java Enum-Всемогущий.
— Как так, Всемогущий? — перебивает Голос с возмущением. — Это только перечисление!
— Ага, — отвечает разраб, Но не только.
— Докажи!
— Enum как гвозди, утильным могёт.
— Во так чудеса, но… Наследника у него нет!
— А это как посмотреть. А кого считать Наследником? Scala? Kotlin?
— Давай пример, не дожидаясь пока разраб завершит свой вопрос
— Да уж, интересные Вы ребята, прогеры — уже улыбаясь, говорит Голос, — Но малова-то будет
Почесав репу, Иван продолжил:
— Enum-то у нас фабрика!
— Не, было уже.
Пришлось, Ивану последний козырь достать:
— Enum-Синглтон, точно!
Выбери свое |
— Джошуа Блох говорит*, что это лучшая реализация Синглтона.
— Ну а ты?
— А что я? Это, это — это синглтон-фабрика, для хранения одного единственного элемента, тить колотить.
Это точка для доступа к массиву для хранения одного единственного элемента, тить колотить.
Немного выводов
Итого получается, что enum можно наделить следующими качествами и свойствами, в зависимости от точки обзора:
Эксперимент
Я решил понять, сколько можно максимально сгенерировать элементов перечислений. Мой собственный ответ и реальность настолько разошлись, что я усомнился в своих знаниях. Прежде чем Вы посмотрите ниже, попытайтесь дать ответ самостоятельно. Упростим, скажите хотя бы порядок? Вот код, который я использовал для генерации класса перечислений (на быструю руку):
Вы уже предположили? Так вот, на семерочке мне удалось сгенерировать всего 2746 элементов перечислений. А дальше вот это:
Но, так как я раскатал губу в 4 этажа, сначала я получил такую ошибку:
Типы перечислений (справочник по C#)
Тип перечисления (или тип enum) — это тип значения, определенный набором именованных констант применяемого целочисленного типа. Чтобы определить тип перечисления, используйте ключевое слово enum и укажите имена элементов перечисления:
Вы не можете определить метод внутри определения типа перечисления. Чтобы добавить функциональные возможности к типу перечисления, создайте метод расширения.
Тип перечисления используется для представления выбора из набора взаимоисключающих значений или комбинации вариантов выбора. Чтобы представить комбинацию вариантов выбора, определите тип перечисления как битовые флаги.
Типы перечислений как битовые флаги
Дополнительные сведения и примеры см. на странице справочника по API System.FlagsAttribute и в разделе о неисключительных элементах и атрибутах Flags страницы справочника по API System.Enum.
Тип System.Enum и ограничение перечисления
Тип System.Enum является абстрактным базовым классом всех типов перечисления. Он предоставляет различные методы, позволяющие получить информацию о типе перечисления и его значениях. Дополнительные сведения и примеры см. на странице справочника по API System.Enum.
Преобразования
Для любого типа перечисления существуют явные преобразования между типом перечисления и его базовым целочисленным типом. Если вы привели значение перечисления к его базовому типу, то результатом будет связанное целочисленное значение элемента перечисления.
Используйте метод Enum.IsDefined, чтобы определить, содержит ли тип перечисления элемент перечисления с определенным связанным значением.
Для любого типа перечисления существует упаковка — преобразование и распаковка — преобразование в тип System.Enum и из него соответственно.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#: