Для чего нужен 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 в PHP 8.1 — для чего нужен enum, и как реализован в PHP
Зачем нужны enum?
Зачем вообще нужны enum? По сути они служат цели улучшенного описания типов. Давайте рассмотрим пример без енумов и с ними. Допустим, у нас продаются машины трех цветов: красные, черные и белые. Как описать цвет, какой тип выбрать?
Это приводит к тому, что многие php-программисты заводят константы и объединяют их в одном классе, чтобы явно видеть все варианты.
Поэтому здесь нужен не класс, а отдельный тип. Этот тип называется enum
Pure enum
В самом простом случае (pure enum), enum описывается так:
Описав такой тип, мы можем использовать его везде:
Enum со скалярами
Но если бы это было всё, то было бы слишком просто. После названия enum можно указать скалярный тип, например string. И у каждого кейса указать скалярное значение. Это может быть полезно для некоторых целей, например для сортировки или записи в базу данных.
Скалярное значение можно получить потом так:
И наоборот, т.е. получить case через значение, тоже можно:
Помимо полей «case» в enum может быть еще много всего. По сути это разновидность класса. Там могут быть методы, он может реализовывать интерфейсы или использовать трейты.
Кстати, обратите внимание на работу с енамами в конструкции match. Похоже, match затевался именно для них.
Лично я горячо одобряю введение enum в PHP, это очень удобно и читабельно, и в большинстве языков, где есть какие-никакие типы, enum уже давно есть (кроме, разве что Go).
Дальше — больше. Tagged Unions (тип-сумма)
Есть RFC, которые развивают идею enums дальше, чтобы можно было хранить в одном enum значения разных типов. Как в языке Rust, например. Тогда можно будет сделать, допустим, enum Result с двумя case-ами Result::Ok и Result::Err, причем эти объекты будут хранить данные: Ok будет хранить результат, а Err — ошибку, у каждого из этих значений будет свой тип.
И всё это не в Расте или Хаскеле, а в PHP!
Об этом мы расскажем в деталях в ближайших постах телеграм-канала Cross Join, не забудьте подписаться.
Быстрый ENUM
Зачем нужно перечисление (enum)
(если вы все знаете — опуститесь до секции «Перечисления в стандартной библиотеке»)
Представьте, что вам нужно описать набор всех возможных состояний сущностей в собственной модели базы данных. Скорее всего, вы возьмёте пачку констант, определенных прямо в пространстве имен модуля:
… или как статические атрибуты класса:
Такой подход поможет сослаться на эти состояния по мнемоническим именам, в то время как в вашем хранилище они будут представлять собой обычные целые числа. Таким образом вы одновременно избавляетесь от магических чисел, разбросанных по разным участкам кода, заодно делая его более читабельным и информативным.
Или же ваш класс становится таким:
Наконец, именованный кортеж превращается в:
Уже неплохо — теперь он гарантирует, что и значение состояния и заглушка перевода отображаются на языки поддерживаемые UI. Но вы можете заметить, что код, использующий эти отображения, превратился в бардак. Каждый раз, пытаясь присвоить значение сущности, приходится извлекать значение с индексом 0 из используемого вами отображения:
илиили
И так далее. Помните, что первые два подхода, использующие константы и атрибуты класса, соответственно, страдают от изменяемости.
И вот перечисления приходят к нам на помощь
Вот и все. Теперь вы можете легко перебирать перечисление в вашем рендере (синтаксис Jinja2):
Перечисление является неизменяемым как для набора элементов — нельзя определить новый член перечисления во время выполнения и нельзя удалить уже определенный член, так и для тех значений элементов, которые он хранит — нельзя [пере]назначать любые значения атрибута или удалять атрибут.
В вашем коде вы просто присваиваете значения вашим сущностям, вот так:
Все достаточно понятно, информативно и расширяемо. Вот для чего мы используем перечисления.
Как мы смогли сделать его быстрее?
Перечисление из стандартной библиотеки довольно медленное, поэтому мы спросили себя — можем ли мы ускорить его? Как оказалось — можем, а именно, реализация нашего перечисления:
Slots
К примеру, можно использовать объявление класса с помощью __slots__ — в этом случае все экземпляры классов будут иметь только ограниченный набор свойств, объявленных в __slots__ и всех __slots__ родительских классов.
Descriptors
По умолчанию интерпретатор Python возвращает значение атрибута объекта напрямую (при этом оговоримся, что в данном случае значение — это тоже объект Python, а не, например, unsigned long long в терминах языка Си):
value = my_obj.attribute # это прямой доступ к значению атрибута по указателю, который объект хранит для этого атрибута.
Перечисления в стандартной библиотеке
Таким образом, вся последовательность вызовов может быть представлена следующими псевдокодом:
Мы написали простой скрипт демонстрирующий вывод, описанный выше:
И после выполнения скрипт выдал нам следующую картинку:
Сравните с нашим FastEnum:
Что видно на следующем изображении:
Все это действительно происходит внутри стандартной реализации перечислений каждый раз, когда вы обращаетесь к свойствам name и value их членов. Это же и причина, по которой наша реализация быстрее.
Наш подход
Свою реализацию перечислений мы создавали с оглядкой на элегантные перечисления в C и прекрасные расширяемые перечисления в Java. Основные функции, которые мы хотели реализовать у себя, были следующими:
Каковы дополнительные фишки?
FastEnum не совместим ни с какой версией Python до 3.6, поскольку повсеместно использует аннотации типов, внедренные в Python 3.6. Можно предположить, что установка модуля typing из PyPi поможет. Краткий ответ — нет. Реализация использует PEP-484 для аргументов некоторых функций, методов и указателей на тип возвращаемого значения, поэтому любая версия до Python 3.5 не поддерживается из-за несовместимости синтаксиса. Но, опять же, самая первая строка кода в __new__ метакласса использует синтаксис PEP-526 для указания типа переменной. Так что Python 3.5 тоже не подойдет. Можно перенести реализацию на более старые версии, хотя мы в Qrator Labs, как правило, используем аннотации типов когда это возможно, так как это сильно помогает в разработке сложных проектов. Ну и в конце-концов! Вы же не хотите застрять в Python до версии 3.6, поскольку в более новых версиях нет обратной несовместимости с вашим существующим кодом (при условии, что вы не используете Python 2), а ведь в реализации asyncio была проделана большая работа по сравнению с 3.5, на наш взгляд, стоящая незамедлительного обновления.
Однако, существуют некоторые ограничения: все имена членов перечисления должны быть написаны ЗАГЛАВНЫМИ буквами, иначе они не будут обработаны метаклассом.
Наконец, вы можете объявить базовый класс для ваших перечислений (имейте в виду, что базовый класс может сам использовать метакласс, поэтому вам не нужно предоставлять метакласс всем подклассам) — достаточно определить общую логику (атрибуты и методы) в этом классе и не определять членов перечисления (так что класс не будет «финализирован»). После можно объявить столько наследующих классов этого класса, сколько захотите, а сами наследники при этом будут иметь общую логику.
Псевдонимы и как они могут помочь
Предположим, что у вас есть код, использующий:
И что класс MyEnum объявлен следующим образом:
Теперь, вы решили что хотите сделать кое-какой рефакторинг и перенести перечисление в другой пакет. Вы создаете что-то вроде этого:
Где MyMovedEnum объявлен так:
Вот и все. При перезапуске ваших приложений на этапе unpickle все члены перечисления будут переобъявлены как экземпляры MyMovedEnum и станут связаны с этим новым классом. В тот момент, когда вы будете уверены, что все ваши хранимые, например, в базе данных, объекты были повторно десериализованы (и, возможно, сериализованы опять и сохранены в хранилище) — вы можете выпустить новый релиз, в котором ранее помеченный как устаревший класс MyEnum может быть объявлен более ненужным и удаленным из кодовой базы.
Перечисления (enum): для чего они нужны? Как, когда и какими перечислениями уместнее пользоваться?
Для чего нужны сужающие преобразования как они работают и на сколько они важны?
Я читаю одну книгу и застрял на одной теме «Преобразования», и там есть такой вот код double х.
Что за драйвера такие, для чего они и нужны ли они вообще?
Что за драйвера такие, для чего они и нужны ли они вообще? 1 Intel SATA Preinstall driver (For.
Зачем нужны перечисления enum?
Зачем нужны перечисления enum? Почему нельзя просто использовать массив? Объясните в чем плюсы.
Struts для чего они нужны?
Привет всем! Сразу прошу не бить ногами, если кому-то вопрос показался идиотским. Но дело в том.
Решение
Добавлено через 1 минуту
Photofenix, можно задать безымянный enum:
Так вот, такие состояния мне было удобно хранить с помощью переменной типа enum, в списке констант которого я указывал наименования состояний. В данном случае, значения самих констант меня не интересовали (смешно, но преподаватель по какой-то причине хотела забраковать такой подход, показав, что я ошибаюсь (типа значения этих констант всё-таки играют роль в моём коде), но, переставив несколько значений в моём списке, не добилась своего).
Ну и, конечно, удобно просто называть состояния своими именами, а не 1, 2, 3.
Модераторы\администраторы, почему съедаются слова в уже написанном тексте? CTRL+Z, вроде, не нажимал.
Добавлено через 2 часа 24 минуты
Гайд по использованию enum в Python
Модуль enum содержит в себе тип для перечисления значений с возможностью итерирования и сравнения. Его можно использовать для создания понятных обозначений вместо использования чисел (для которых приходится помнить, какое число что обозначает) или строк (в которых легко опечататься и не заметить).
Создание
Итерирование
При итерировании по классу вы пройдёте по атрибутам.
Цикл будет идти по элементам в том порядке, в котором они указаны при создании класса. Названия и значения никак не влияют на порядок.
Сравнение перечислений
Так как элементы перечислений не упорядочены, то они поддерживают сравнение только по названию или значению.
IntEnum
Уникальные значения в перечислениях
Элементы перечисления с одинаковыми значениями являются несколькими названиями, указывающими на один и тот же объект.
Так как by_design и closed являются синонимами для других элементов, то они не появляются как элементы в циклах. Истинным считается название, указанное первым при объявлении.
Если вы хотите, чтобы все элементы обязательно имели разные значения, то добавьте декоратор @unique перед объявлением класса.
Элементы с повторяющимися значениями будут вызывать ValueError во время интерпретации.
Другой способ создания перечислений
Иногда удобнее не хардкодить элементы перечисления, а указывать их в более удобном виде. Для этого можно передать значения в конструктор класса:
Аргумент value является названием перечисления, которое используется для создания представления элементов. Второй аргумент names принимает список названий в перечислении. Если подать одну строку, то она будет разбита по пробельным символам и запятым, а значения будут числами, начиная с 1.
Также аргумент name принимает список пар название — значение, либо аналогичный словарь.
Передача списка пар позволяет сохранить порядок элементов, аналогично случаю, где мы объявляли атрибуты.
Другие типы значений
В этом примере каждое значение является парой из числового id и списка строк, описывающих возможный переход из данного состояния.
Данный пример аналогичен предыдущему, но в нём используются словари вместо tuple для удобства.