Подводные камни enum
От: varenikAA  
Дата: 08.05.19 03:44
Оценка:
В процессе работы заметил, что
enum довольно "слабый" тип, который скорее является простым алиасом/набором констант для
численных типов.
Так, доступим есть тип
 
enum Month
{
    Jan = 1,
    Feb = 2
}

тогда, если
var zero = default(Month); //=> 0

или
var m = (Month)10; //=> 10!!!
var t = m.GetType(); //=> Month!!!


Т.е. что первый код, что второй приводят к скрытой ошибке.
Таким образом, мы вынужденны всегда проверять значение через
Enum.IsDefined

, что согласитесь неудобно.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 08.05.2019 3:46 Разраб . Предыдущая версия .
Re: Подводные камни enum
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.05.19 05:10
Оценка: 3 (1) +2
Здравствуйте, varenikAA, Вы писали:

AA>В процессе работы заметил, что

AA>enum довольно "слабый" тип, который скорее является простым алиасом/набором констант для
AA>численных типов.
AA>Так, доступим есть тип
AA>
 
AA>enum Month
AA>{
AA>    Jan = 1,
AA>    Feb = 2
AA>}
AA>

AA>тогда, если
AA>
AA>var zero = default(Month); //=> 0
AA>

AA>или
AA>
AA>var m = (Month)10; //=> 10!!!
AA>var t = m.GetType(); //=> Month!!!
AA>


AA>Т.е. что первый код, что второй приводят к скрытой ошибке.

Пока непонятно, в чём именно ошибка.
AA>Таким образом, мы вынужденны всегда проверять значение через
AA>
Enum.IsDefined

AA>, что согласитесь неудобно.
Примерно также реализованы енумы в С/С++. На выбор реализации енумов в дотнет существенно повлияли соображения интероперабельности с неуправляемым кодом.
К примеру, Flags-енумы вполне легально включают значения, не сводимые ни к одному из членов перечисления.
Теоретически, можно было бы разделить енумы на два подкласса — flags и unique; но это скорее затруднит, чем облегчит, их использование. И весьма существенно увеличит стоимость реализации.
Например, CLR устроен так, что все value-типы инициализируются битовыми нулями. Но вы совершенно не обязаны включать значение 0 в перечисление:
public enum Fail {Correct = 1}; 
...
var fail = new Fail[1]; //Омг, что у меня в Fail[0]?


Если лично вам очень нужен енум с более жёстким поведением, вы всегда можете его свелосипедить:
public struct StrictEnum<E> where E: struct, Enum
{
  private E _value;
  public StrictEnum(E value) 
  {
    if(!Enum.IsDefined(typeof(E), value))
    throw new ArgumentException();
    _value = value;
  }
  ...
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 11.05.2019 5:39 Sinclair . Предыдущая версия . Еще …
Отредактировано 08.05.2019 6:41 Sinclair . Предыдущая версия .
Re[2]: Подводные камни enum
От: varenikAA  
Дата: 08.05.19 06:25
Оценка:
Здравствуйте, Sinclair, Вы писали:
S>Пока непонятно, в чём именно ошибка.
Под ошибкой я имел ввиду корректность работы программы,
Если любое значение приводится к указанному типу,
то приведение должно содержать допустимое значение,
согласно документации все правильно, но не с точки зрения здравого смысла.

Для чего пользователь определяет тип?
Чтобы хранить данные определенного вида.
Не хочется думать, что перечисление описывающее месяца
может принимать значение отличное от 1-12.
По моему мнению, тогда уж лучше для наглядности использовать список кортежей, например: [(1, "Jan", 31);(2,"Feb",28)...] и пара методов: byNum и byValue.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[3]: Подводные камни enum
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.05.19 07:27
Оценка:
Здравствуйте, varenikAA, Вы писали:

AA>Здравствуйте, Sinclair, Вы писали:

S>>Пока непонятно, в чём именно ошибка.
AA>Под ошибкой я имел ввиду корректность работы программы,
AA>Если любое значение приводится к указанному типу,
AA>то приведение должно содержать допустимое значение,
AA>согласно документации все правильно, но не с точки зрения здравого смысла.
Здравый смысл — штука субъективная. Что должно происходить с точки зрения здравого смысла вот в таком фрагменте кода:
var sh = FileShare.Read | FileShare.Write | FileShare.Delete


AA>Для чего пользователь определяет тип?

AA>Чтобы хранить данные определенного вида.
Не совсем. Обычно — чтобы манипулировать данными определённого вида.
AA>Не хочется думать, что перечисление описывающее месяца
AA>может принимать значение отличное от 1-12.
Проблема — в том, что обеспечить вот это ваше пожелание достаточно тяжело. Поэтому в платформу не включили вот этот хардкор.
Настоящий безопасный енум можно сконструировать в виде класса — и даже универсальным образом. Способ я вам показал (только надо заменить struct на class).
Ценой этого будут проверки после каждой операции с таким енумом.
AA>По моему мнению, тогда уж лучше для наглядности использовать список кортежей, например: [(1, "Jan", 31);(2,"Feb",28)...] и пара методов: byNum и byValue.
Всё зависит от сценариев использования. Попробуйте открыть любой C# — проект с гитхаба, и посмотрите, сколько там использований enum, и сколько раз встречается enum.IsDefined().
Если вы правы, то таких мест будет очень много. В доступной мне codebase такой вызов — ровно один:
    public static DomainVerificationMode ConvertDomainVerificationMode(int mode)
    {
        return Enum.IsDefined(typeof(DomainVerificationMode), (byte)mode)
            ? (DomainVerificationMode)mode
            : DomainVerificationMode.Txt;
    }

Он применяется при ручной трансформации данных из одного формата в другой.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Подводные камни enum
От: AlexRK  
Дата: 08.05.19 08:21
Оценка: +1
Здравствуйте, varenikAA, Вы писали:

AA>enum довольно "слабый" тип, который скорее является простым алиасом/набором констант для

AA>численных типов.

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

Можно и так кастануть: "(DateTime)((object)((string)s))", что, по сути, есть аналог приведения числа к енуму — только в этом случае преобразование никогда не имеет смысла, а для енума _иногда_ имеет.

Зло — это каст, а не енум.

AA>Т.е. что первый код, что второй приводят к скрытой ошибке.

AA>Таким образом, мы вынужденны всегда проверять значение через
AA>
Enum.IsDefined

AA>, что согласитесь неудобно.

Увы, запретить прямое приведение вряд ли возможно (по крайней мере, я не знаю, как). Можно накостылить свой говноаналог енума, что тоже криво. Или просто забить и сделать вид, что всё хорошо, как все и делают.
Re: Подводные камни enum
От: Vladek Россия Github
Дата: 08.05.19 08:33
Оценка:
Здравствуйте, varenikAA, Вы писали:

AA>В процессе работы заметил, что

AA>enum довольно "слабый" тип, который скорее является простым алиасом/набором констант для
AA>численных типов.
Пиши собственные типы. Студия последних версий за полминуты поможет тебе накидать полноценную структуру, скрывающую примитивный тип внутри себя.

AA>, что согласитесь неудобно.

В перечислениях всегда имей константу для 0 — Unknown, None, Undefined и тому подобные.
Re[2]: Подводные камни enum
От: pugv Россия  
Дата: 08.05.19 08:44
Оценка:
Здравствуйте, Sinclair, Вы писали:

if(!= Enum.IsDefined(typeof(E), value))


!= это опечатка или очередной синтаксический порошок?
Re[3]: Подводные камни enum
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.05.19 09:13
Оценка:
Здравствуйте, pugv, Вы писали:

P>!= это опечатка или очередной синтаксический порошок?

Опечатка. Чего-то у меня FF стал адски корячить тексты после восстановления внезапно упавшей страницы.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: FileShare
От: Sharov Россия  
Дата: 08.05.19 09:31
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравый смысл — штука субъективная. Что должно происходить с точки зрения здравого смысла вот в таком фрагменте кода:

S>
S>var sh = FileShare.Read | FileShare.Write | FileShare.Delete
S>


А что тут такого, мы ожидаем, что во время работы с файлом может быть все, что угодно. Тут главное все сюрпризы грамотно обработать.
Кодом людям нужно помогать!
Re[2]: Подводные камни enum
От: MadHuman Россия  
Дата: 08.05.19 09:49
Оценка:
Здравствуйте, Vladek, Вы писали:

AA>>, что согласитесь неудобно.

V>В перечислениях всегда имей константу для 0 — Unknown, None, Undefined и тому подобные.

а зачем? можно там где это имеет смысл так
MyEnum?

плюс в том, что сразу видно что допустим вариант — нет значения.
Re[5]: FileShare
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.05.19 10:34
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:


S>>Здравый смысл — штука субъективная. Что должно происходить с точки зрения здравого смысла вот в таком фрагменте кода:

S>>
S>>var sh = FileShare.Read | FileShare.Write | FileShare.Delete
S>>


S>А что тут такого, мы ожидаем, что во время работы с файлом может быть все, что угодно. Тут главное все сюрпризы грамотно обработать.

Отож. Но ведь Enum.IsDefined(typeof(FileShare), sh) вернёт false / ТС считает, что тут — скрытая ошибка.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: Подводные камни enum
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.05.19 10:37
Оценка:
Здравствуйте, MadHuman, Вы писали:
MH>а зачем?
Чтобы свежесконструированное значение имело имя
var t = (new MyEnum[1])[0]; // чему равно t?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Подводные камни enum
От: · Великобритания  
Дата: 08.05.19 10:54
Оценка:
Здравствуйте, Sinclair, Вы писали:

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

S>Здравый смысл — штука субъективная. Что должно происходить с точки зрения здравого смысла вот в таком фрагменте кода:
S>var sh = FileShare.Read | FileShare.Write | FileShare.Delete

С т.з. практики... да, иногда полезно иметь такой enum.
А с т.з. здравого смысла тип должен быть совсем другой, Set<FileShare> или вроде того.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Подводные камни enum
От: Kolesiki  
Дата: 08.05.19 13:38
Оценка:
Здравствуйте, varenikAA, Вы писали:

AA>В процессе работы заметил, что enum довольно "слабый" тип

AA>var zero = default(Month); //=> 0
AA>Т.е. что первый код, что второй приводят к скрытой ошибке.

Пустые жалобы на извращенческие случаи. 99.9999% использований енума — замена "неудобных чисел" на гибкие имена, причём эти имена используются прогером осознанно в коде.
Если ты вдруг решил взять число из недоверенного источника — ты ОБЯЗАН проверять его, прежде чем кастить к енуму — это нормальная практика обращения с такими данными. В чём проблема-то?? Что ты сам себе придумываешь грабли? Нужен параноидальный язык — велкам ту Ada.
Re[4]: Подводные камни enum
От: MadHuman Россия  
Дата: 08.05.19 13:46
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, MadHuman, Вы писали:

MH>>а зачем?
S>Чтобы свежесконструированное значение имело имя
S>var t = (new MyEnum[1])[0]; // чему равно t?
0, но это следствие текущего дизайна енумов.

с точки зрения системы типов, лучше явно выражать, что переменная допускает пустое значение и значения енума, либо только значения енума.
ведь нет к примеру пола None, есть либо М либо Ж.
и есть спец. случай — пол неопределён, но это касается не пола, а конкретного места (переменной/поля/аргумента).
и с помощью системы типов мы выражаем что либо допустимо тут пустое значение, либо нет.

это аналогично по сути движению по запрету null для refrerence типов, точнее чтоб специальным образом через типы выражать когда может быть null а когда нет.
Re[5]: Подводные камни enum
От: varenikAA  
Дата: 08.05.19 14:12
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>Здравствуйте, Sinclair, Вы писали:


S>>Здравствуйте, MadHuman, Вы писали:

MH>>>а зачем?
S>>Чтобы свежесконструированное значение имело имя
S>>var t = (new MyEnum[1])[0]; // чему равно t?
MH>0, но это следствие текущего дизайна енумов.

MH>с точки зрения системы типов, лучше явно выражать, что переменная допускает пустое значение и значения енума, либо только значения енума.

MH>ведь нет к примеру пола None, есть либо М либо Ж.
MH>и есть спец. случай — пол неопределён, но это касается не пола, а конкретного места (переменной/поля/аргумента).
MH>и с помощью системы типов мы выражаем что либо допустимо тут пустое значение, либо нет.

MH>это аналогично по сути движению по запрету null для refrerence типов, точнее чтоб специальным образом через типы выражать когда может быть null а когда нет.


Что самое интересное, в F# о корректности типов которого есть целый сайт, так же можно enum-у присвоить некорректное значение.
Хотя на упомянутом сайте fsharpforfunandprofit.com F# представляется как инструмент обеспечивающий валидацию всего и вся.
В нем даже отсутствует неявное приведение и значение по умолчанию(если мне память не изменяет).
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 08.05.2019 14:12 Разраб . Предыдущая версия .
Re[6]: Подводные камни enum
От: MadHuman Россия  
Дата: 08.05.19 15:27
Оценка:
Здравствуйте, varenikAA, Вы писали:


AA>Что самое интересное, в F# о корректности типов которого есть целый сайт, так же можно enum-у присвоить некорректное значение.

Если пользоваться C# енумо-ом то да. Так сделано для совместимости и поддержки нативного енума дот-нета.
Но если объявлять так:
type Sex = Male|Female

то всё ок, это как раз то, что вы хотите в стартовом топике.
Re[5]: Подводные камни enum
От: Vladek Россия Github
Дата: 08.05.19 19:49
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>с точки зрения системы типов, лучше явно выражать, что переменная допускает пустое значение и значения енума, либо только значения енума.

MH>ведь нет к примеру пола None, есть либо М либо Ж.

Угу: https://ru.wikipedia.org/wiki/%D0%A1%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BE_%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D1%8E_2#/media/File:Venn0110.svg

Как называется дырка в середине? Правильно, анус. Из которого лезет говнокод, который скуп на константы, но богат на неопределённости и неоднозначности.

Если существует только два пола в твоей предметной области, то какой из них будет неявным? Мужской, женский? "Не знаю, пусть пользователь сам выберет" — это уже не М и не Ж, это третий вариант.

MH>и есть спец. случай — пол неопределён, но это касается не пола, а конкретного места (переменной/поля/аргумента).


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

MH>и с помощью системы типов мы выражаем что либо допустимо тут пустое значение, либо нет.


MH>это аналогично по сути движению по запрету null для refrerence типов, точнее чтоб специальным образом через типы выражать когда может быть null а когда нет.


Техническая чепуха, не имеющая отношения к тому, что изначально пол не выбран.
Re[5]: Подводные камни enum
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.05.19 06:47
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>с точки зрения системы типов, лучше явно выражать, что переменная допускает пустое значение и значения енума, либо только значения енума.

Либо вообще любые значения
MH>ведь нет к примеру пола None, есть либо М либо Ж.
Ну, насчёт пола сейчас вопрос болезненный. А с точки зрения enum нужно либо делать { F = 0, M = 1}, либо готовиться к тому, что пол будет никаким.
MH>и есть спец. случай — пол неопределён, но это касается не пола, а конкретного места (переменной/поля/аргумента).
MH>и с помощью системы типов мы выражаем что либо допустимо тут пустое значение, либо нет.
MH>это аналогично по сути движению по запрету null для refrerence типов, точнее чтоб специальным образом через типы выражать когда может быть null а когда нет.
Это будет совсем другая система типов, и совсем другой дизайн enum.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Подводные камни enum
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.05.19 06:53
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, Sinclair, Вы писали:


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

S>>Здравый смысл — штука субъективная. Что должно происходить с точки зрения здравого смысла вот в таком фрагменте кода:
·>
S>>var sh = FileShare.Read | FileShare.Write | FileShare.Delete
·>

·>С т.з. практики... да, иногда полезно иметь такой enum.
·>А с т.з. здравого смысла тип должен быть совсем другой, Set<FileShare> или вроде того.
Ок. Какие будут доступны приведения для этого типа? Можно ли будет его приводить к int и обратно? Явно? Неявно?
Можно ли будет приводить FileShare к Set<FileShare>?
Какого типа будет FileShare.ReadWrite?

Енумы в дотнете сделаны с точки зрения практичности, а не с точки зрения теоретической чистоты.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.