Pull to refresh

Comments 31

Спасибо за статью, но если делаете туториал, то лучше давать сразу best practices. Например:

Конечно первый вид опечатки можно исправить дополнительной операцией приведения к нижнему регистру обеих строк перед сравнением.

А можно использовать string.Compare(str1, str2, StringComparison.OrdinalIgnoreCase).

Для проверки того, определено ли значение enum - лучше использовать Enum.IsDefined. Если нужно огласить весь список - Enum.GetValues. Также ничего не сказано про флаги, и про приколы с боксингом для методов Enum, и почему оно вообще так было сделано. Также очень интересно, что enum не реализует IEquatable / IComparable, что ограничивает его применимость в дженериках (см например https://github.com/dotnet/runtime/issues/17456).

Спасибо за комментарий. О флагах я вспомнил уже сильно после отправки статьи на модерацию. О многих других аспектах я не задумывался вовсе. Я постараюсь изучить эти моменты и вплести их в статью.

Двумя ногами и руками за, меня после С++ вообще переполнило негодование. Вопрос у меня возник "Enum" в C# сделали для вывода списка табличек? Вот правда, но данная функция используется реже всех на свете функций, которые можно делать с Enum.
1) Чтобы работать с табличками нужны рефлексии и это используется еще реже

2) Чтобы вывести в терминал список значений - используется разве что в графических интерфейсах и/или для какого-то дебаггинга потому что обычно название табличек следует правилам оформления названия переменных и для интерфейса пользователя выглядит как-то не очень (разве что для какой-то утилиты на коленке для себя)

3) Метод HasFlag() > Flag1 &Flag2 == Flag2 реализовали

Метод HasAnyFlag() > Flag1 & Flag2 != 0 не реализовали.

В итоге если вам просто нужно сделать тривиальную проверку на биты - делайте ручками или лепите пулю из каки.

4) Как уже автор сказал Enum вообще не наследует никакие полезные интерфейсы, которые есть у обычных значимых типов. Ситуация накалилась после выхода Net.7. Разработчики объявили что они оптимизировали HasFlag(), но как уже сказано этой функции недостаточно. Любые попытки создать универсальный метод с Enum заставляет вас использовать боксинги, рефлексии или такие костыли как передача Enum в качестве long и далее работа с базовыми структурами.

5) Весьма непонятно почему не реализовано Implicit преобразование Enum в более значимые типы или равнозначные к UnderlyingType

6) Непонятно почему нельзя было как-то на уровне IDE оптимизировать оформление Enum чтобы они считались обычными базовыми типами, а все остальное было бы просто спрятано под алгоритмами IDE. Enum нельзя выбрать как значение по умолчанию, так как считается что это переменная времени исполнения.

В итоге такое ощущение что этот тип придумали ради того чтобы просто на курсах по C# для новичков показывать "какой крутой и мощный C#" . А на деле людям приходится создавать свои статические классы со статическими именованными константами для того чтобы их передавать как значение по умолчанию или как параметр в атрибуты.

И печально то, что особо никто не парится об этом и никаких подвижек в сторону переработки этой структуры нет и уже не будет (ведь как же совместимость)

Жду когда в каком-то .НЕТ100500 создадут адекватную штуковину.

У меня стойкое ощущение, что этот комментарий был ответом на что-то другое :)

Тем не менее. Совсем неясно, о каких табличках идет речь. enum в C# в целом используется точно так же, как и в других языках, где он есть. Значение по умолчанию для enum работает точно так же, как и для примитивных типов. Только если вы не путаете enum и Enum, который в чистом виде никто не использует.

>>Также ничего не сказано про флаги, и про приколы с боксингом для методов Enum, и почему оно вообще так было сделано. Также очень интересно, что enum не реализует IEquatable / IComparable, что ограничивает его применимость в дженериках <<

ответ был в поддержку этого, а таблички я назвал табличками, потому что так их назвал автор статьи. Табличка у него это как раз "имя" которое привязано к значению.

Читый Enum никто не использует до тех пора не нужно сделать какой-то Дженерик

Я бы предложил дополнить статью очень большим коварством енама - его сериализацией.
Некоторые сериализаторы могут сериализовать enum как число. И вот тут будет большой косяк, если в ходе обновления кода в старый енам добавится новое значение где то в середине (что вполне норма если енам упорядочен по какой то логике для красоты) - все старые сериализованные данные съедут напрочь.

если в ходе обновления кода в старый енам добавится новое значение где то в середине

Жесть какая то, думаю никто в здравом уме такого делать не будет.

Если речи идёт об изменении значения, а не просто о вставке нового значения между уже существующими (типа добавить 35 между 30 и 40)

Легко могут. Забыли месяц в списке месяцев, добавился язык и решили упорядочить по региональному принципу.

Но вообще про добавление это пример. Посыл простой - последовательность записей в енаме может оказаться очень важной именно в контексте сериализации данных.

Кстати верно и обратное - если сериализатор сериализует строками а не интами - то нельзя рефакторить енам бездумно.

В общем то коварств много ) И раз уж в статье решили покрывать всякие мелочи - то и эту "мелочь" стоит упомянуть.

И не только сериализаторы. При проекции данных из БД в объекты при помощи Dapper можно получить схожий сценарий, если значения целочисленного поля в БД не соответствуют значениям в перечислении. В т.ч. можно получить enum-свойство, значение которого вовсе отсутствует в перечислении.

Код скриншотами - это восторг, конечно.

Не раскрыта куча всего интересного, например, самое банальное - почему в методе X(SomeEnum v) на входе может оказаться значение, которое не входит в перечисление SomeEnum (и поэтому в switch нужно иметь default-ветку).

По коду на скринах согласен, не лучшее решение, постараюсь это поправить. Про дополнительные значения перечисления я прямо указывал в куске про каст числа в енам, если я правильно понял суть претензии (никаких негативных коннотаций). Необходимость проставления default в switch где-либо (в том числе для енама) я воспринимаю как саму собой разумеющуюся конструкцию.

Про дополнительные значения перечисления я прямо указывал в куске про каст числа в енам

Вы много что "указали", но приведенный мной пример вы не рассматриваете (а он - одна из вещей, в которые новички влетают, пока не поймут, как это работает). Каст там не обязателен, кстати.

Необходимость проставления default в switch где-либо (в том числе для енама) я воспринимаю как саму собой разумеющуюся конструкцию.

...понимаете ли, в чем дело - разные люди разные вещи воспринимаеют как "само собой разумеющееся". Для меня вот все, что написано в вашем посте про энамы - само собой разумеющееся, кроме куска про, что будет если перемешать явные и неявные значения (потому что у меня это проходит по категории "не надо так делать, поведение не определено"). А кто-то не понимает, почему нужна ветка, захватывающая неизвестные значения. А кто-то не понимает, почему иногда можно обойтись без default.

Я понимаю ваше негодование. Возможной причиной этого я вижу неправильное позиционирование этой статьи в силу её названия. Статья не претендует на полноценный гайдлайн по енамам. Она скорее про некоторые интересные штуки, обнаруженные мою в ходе работы с перечислениями. Я подумаю как лучше назвать статью, чтобы она не претендовала на главу из Рихтера.

Она скорее про некоторые интересные штуки, обнаруженные мою в ходе работы с перечислениями.

...тогда зачем нужна вся остальная статья, которая этих "штук" не касается?

Сам по себе енам для меня уже является "интересной штукой", если уж на то пошло. Да и надо было с чего-то начать статью, вот я и решил дать небольшую вводную.

То, что что-то интересно для вас, еще не значит, что оно интересно всем остальным (и при этом еще не описано где-то в другом месте, лучше и подробнее).

Я не хочу оскорбить вас своим комментарием, но, по-моему, текст перед скобочками немного не сочетается с текстом непосредственно в скобках (возможно дело в опечатке в месте "еще не описано"). К вопросу о том, что другим этот текст может быть не интересен: моё дело предложить, а публика пусть уже выбирает. Ваш выбор я услышал, я его уважаю и не хочу его оспаривать. Еще раз спасибо за критику по поводу скринов, я к ней прислушался

Я не хочу оскорбить вас своим комментарием, но, по-моему, текст перед скобочками немного не сочетается с текстом непосредственно в скобках

Текст в скобках относится к случаю, когда что-то, вам интересное, действительно интересно другим - возможно оно уже описано кем-то еще (в случае с enum это "возможно" практически гарантированно).

Я попал в десадовскую группу? Ой, извините.

Я иногда вместо enum использую класс с двумя полями. В нем можно сделать список со всеми значениями и из него добавлять в базу, если этот справочник кроме использования в коде еще может отображаться в интерфейсе.

Некоторые бд поддерживают работу с перечислениями

на кой он вообще нужен?

Единственное для чего нужен enum это чтобы писать говнокод. Первый же пример с цветом это хорошо демонстрирует.

Enum в C# хорошенько так позволяет выстрелить себе в ногу. Надеюсь у автора за всю карьеру ноги были целыми 😂

А в целом статья интересная, многое из этого я не знал. Спасибо!

1) Задавать цвета энамом все же не лучшая идея, если у вас конечно не строго ограничены их варианты и новых иметь никогда не планируется. Если уж очень хочется, то тут на хабре была статья про строгую типизацию, где предлагалось на каждую сущность создавать объект, соответственно тут можно было бы инкапсулировать цвет в объекте Color, который бы, например, инициализировался числовыми значениями для red, green и blue.

2) в случае с енамами городить отдельный метод IsDefindedXXX нет смысла, так как значения энамов проверяются на этапе компиляции, а если значение передаётся строкой, то у класса Enum есть специальный метод

3) битовые флаги проще указывать через битовые сдвиги типа 2 << 1, в этом случае не придётся вычислять значение самостоятельно

4) Для проверки флагов есть метод Enum.HasFlag, нет смысла городить огород с би овыми операциями, с ним код и гораздо читабильнее

5) Флаги все же сущность из мира таких языков, как С++, и нужна по большому счету только для оптимизации. В абсолютном большинстве случаев лучше использовать объект с boolean свойствами, даже в плане понятности и удобства использования

6) Раздел "Приведение числа к Enum" абсолютно непонятен, поскольку заголовок про одно, текст про другое, а пример и вовсе про третье. В целом, число к перчислению приводится обычной операцией приведения типа, задача расширения перечисления без его изменения признак плохой архитектуры, а задачи сортировки перечислений (без привязки к какой либо метаинформации) могут возникнуть только в случае, если перечисление используется не по назначению.

7) Чаще всего энамы не приводят друг к другу (со схожими значениями), а если и приводят, то по значению. Если же хочется изврата, то первый энам можно привести к строке, а потом по ней найти значение во втором (и нет, это делается не перебором всех значений энама и даже цикл для этого не нужен).

В целом статья про то, как не надо использовать перечисления

1) Пример с цветами был выбран скорее из-за его простоты для понимания. Я конечно мог взять пример с набором статусов, но решил, что цвета будут несколько демонстративнее.

2) Метод IsDefined не является методом непосредственно енама, а класса, реализующего в себе этот енам. Метод был добавлен для демонстрации того, почему енам в задаче создания строго заданного списка состояний лучше чем число и строка (как ни странно, я порой наблюдал недоумение пользователей на бордах, почему просто не использовать строку или число)

3-5) Спорить не буду, я не особо силен в части флагов. Этот кусок статьи был создан скорее для того, чтобы указать на неоправданность использования флагов в большинстве случаев. Сам однажды столкнулся с необходимостью распиливать флаги, затрагивавшие половину проекта, на класс с булам. Момент с операциями над флагами я переработаю.

6) Пример с временным расширением диапазона енамов является переработкой реальной задачи, в которой бизнес хотел, чтобы сортировка доступных статусов была кастомной и содержала в себе предыдущий статус, который мог быть разным и брался он из бд.

7) Часть с приведение енамов друг к другу была основана на небольшой проблем, с которой я столкнулся. По ходу задачи было необходимо замапить внешний статус, которых соотносился один ко многим с внутренним (в данной задаче было допустимо получить лишь 1 внутренний статус на 1 внешний). Я без задней мысли создал стандартный маппинг через автомаппер, даже не задумавшись о том, что маппинг происходит не по имени, а по числу. Конечно, можно было бы написать непосредственно про автомаппер, но тогда это была бы уже отдельная статья про автомаппер.

Все это очень плохая практика, так делать нельзя.

  • Нельзя расширять просто так энумы, нужно править каждое использование. Энумы - .

  • Нельзя использовать строки в коде, это данные.

  • Не нужно костылить энумы, так это неочевидное усложнение.

  • Для расширение функционала энумов лучше всего использовать атрибуты. Тогда можно в них хранить дополнительные данные и функции преобразования. И это избавить от лишних условий.

А вообще enum в C# примитивные и ничем не лучше чем в C и один в один их повторяют. А вот за действительно крутыми enum'ами надо идти в Java, Kotlin и Swift. Вот там есть действительно что рассказать и чем удивить.

А вообще, по моему мнению, за крутыми enum нужно идти в rust. Я хоть и шарпист, но мне очень нравится как enum в rust реализованы.

Подскажите пожалуйста с таким нюансом. У меня есть узкое горлышко в программе. Нужно считывать поток данных из стрима и как можно быстрее. Есть карта, по которой определяется какой байт за что отвечает. Для ускорения обработки все поля этой карты выполнены вручную - последовательная пробежка под байтам через индексы и вызов метода записи в нужную переменную.

Есть стрим, в который записываются данные из другого стрима. Их порядок не совпадает, но нужно по возможности автоматизировать.

Если я буду использовать Enum - как delta, чтобы перемещаться по вложенному массиву - компилятор будет создавать константу в коде? или он будет высчитывать эту константу во время выполнения? Это важно опять же из-за соображений скорости.

Пример:
Enum Colour { red, white, blue, green, black }
Enum Item { table, cup, wall, max }
Enum Flat { 401, 200, max }
int[] dataStream = new[(int)Flat.max * (int)Item.max];

int[] Tables= new[Flat.max];

int[] Cups= new[Flat.max];

int[] Walls= new[Flat.max];

int[] Flats= new[tem.max];

writeAllItemsColourForFlatsToStream(int[] dataStream, int startIndex)

{

int delta = (int)Flat.max;

for (int i = 0; i < delta; ++i)

{

int curIndex = startIndex;

dataStream.Write(Tables[curIndex + i]);

curIndex += delta;

dataStream.Write(Cups[curIndex + i]);

curIndex += delta;

dataStream.Write(Walls[curIndex + i]);

}

}

Мне желательно сделать все через Enum, но если мой индекс будет опеределяться во время исполнения - я потеряю в скорости.
Если же я сделаю через константы - тогда теряется смысл в самом C# , так как у меня везде будут int вместо именованного типа. А мои Enum существуют и используются активно другими классами, для которых этот вопрос не стоит ребром.

Добрый вечер. Возможно это уже не актуально, но судя по рихтеру (глава 15) енамы компилятся в список констант

Sign up to leave a comment.

Articles