У меня есть switch
структура, которая должна обрабатывать несколько случаев. switch
Работает над enum
которой ставит вопрос о дублировании кода через объединенные значения:
// All possible combinations of One - Eight.
public enum ExampleEnum {
One,
Two, TwoOne,
Three, ThreeOne, ThreeTwo, ThreeOneTwo,
Four, FourOne, FourTwo, FourThree, FourOneTwo, FourOneThree,
FourTwoThree, FourOneTwoThree
// ETC.
}
В настоящее время switch
структура обрабатывает каждое значение отдельно:
// All possible combinations of One - Eight.
switch (enumValue) {
case One: DrawOne; break;
case Two: DrawTwo; break;
case TwoOne:
DrawOne;
DrawTwo;
break;
case Three: DrawThree; break;
...
}
Вы поняли идею там. В настоящее время у меня это разбито на составную if
структуру для обработки комбинаций одной строкой:
// All possible combinations of One - Eight.
if (One || TwoOne || ThreeOne || ThreeOneTwo)
DrawOne;
if (Two || TwoOne || ThreeTwo || ThreeOneTwo)
DrawTwo;
if (Three || ThreeOne || ThreeTwo || ThreeOneTwo)
DrawThree;
Это ставит вопрос о невероятно длинных логических оценках, которые непонятны для чтения и которые трудно поддерживать. После рефакторинга это я начал думать об альтернативах и думал об идее switch
структуры с провалом между случаями.
Я должен использовать goto
в этом случае, так C#
как не позволяет провалиться. Тем не менее, он предотвращает невероятно длинные логические цепочки, даже если он перепрыгивает в switch
структуре, и все равно вносит дублирование кода.
switch (enumVal) {
case ThreeOneTwo: DrawThree; goto case TwoOne;
case ThreeTwo: DrawThree; goto case Two;
case ThreeOne: DrawThree; goto default;
case TwoOne: DrawTwo; goto default;
case Two: DrawTwo; break;
default: DrawOne; break;
}
Это все еще не достаточно чистое решение, и с goto
ключевым словом связана стигма, которую я хотел бы избежать. Я уверен, что должен быть лучший способ убрать это.
Мой вопрос
Есть ли лучший способ справиться с этим конкретным случаем, не влияя на читаемость и ремонтопригодность?
источник
goto
когда структура высокого уровня не существует на вашем языке. Я иногда хотел бы, чтобы былfallthru
; ключевое слово, чтобы избавиться от этого конкретного использования,goto
ну да ладно.goto
высокого уровня, такой как C #, то вы, вероятно, упустили множество других (и лучших) вариантов проектирования и / или реализации. Очень обескуражен.Ответы:
Я нахожу код трудно читать с
goto
заявлениями. Я бы порекомендовал структурировать ваш по-enum
другому. Например, если выenum
представляли собой битовое поле, где каждый бит представлял один из вариантов, это могло бы выглядеть так:Атрибут Flags сообщает компилятору, что вы устанавливаете значения, которые не перекрываются. Код, который вызывает этот код, может установить соответствующий бит. Затем вы можете сделать что-то вроде этого, чтобы прояснить, что происходит:
Для этого требуется код, который устанавливает
myEnum
правильные битовые поля и помечается атрибутом Flags. Но вы можете сделать это, изменив значения перечислений в вашем примере на:Когда вы пишете число в форме
0bxxxx
, вы указываете его в двоичном виде. Итак, вы можете видеть, что мы установили бит 1, 2 или 3 (ну, технически 0, 1 или 2, но вы поняли идею). Вы также можете называть комбинации с помощью побитового ИЛИ, если комбинации могут часто устанавливаться вместе.источник
public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };
. Не нужно настаивать на C # 7 +.[Flags]
Атрибут ничего компилятору не сигнализировать. Вот почему вы все еще должны явно объявить значения перечисления как степени 2.HasFlag
является описательным и явным термином для выполняемой операции и абстрагирует реализацию от функциональности. Использование,&
потому что оно распространено в других языках, не имеет больше смысла, чем использованиеbyte
типа вместо объявленияenum
.IMO корень проблемы в том, что этот кусок кода даже не должен существовать.
Очевидно, у вас есть три независимых условия и три независимых действия, которые необходимо выполнить, если эти условия выполняются. Так почему же все это вкладывается в один кусок кода, которому нужны три логических флага, чтобы указать ему, что делать (независимо от того, запутываете ли вы их в перечисление), и затем происходит некоторая комбинация из трех независимых вещей? Принцип единой ответственности, похоже, имеет выходной день здесь.
Поместите вызовы к трем функциям, которые принадлежат (то есть, где вы обнаружите необходимость выполнения действий) и отправьте код в этих примерах в корзину.
Если бы было десять флагов и действий не три, вы бы расширили этот вид кода для обработки 1024 различных комбинаций? Надеюсь нет! Если 1024 слишком много, 8 тоже слишком много по той же причине.
источник
Никогда не используйте gotos - это одна из концепций информатики "лгать детям" . Это правильный совет в 99% случаев, а иногда это не так редко и специализировано, что гораздо лучше для всех, если его просто объяснить новым кодировщикам как «не используйте их».
Поэтому , когда следует их использовать? Есть несколько сценариев , но основной, который вам кажется удачным: когда вы кодируете конечный автомат . Если нет лучшего, более организованного и структурированного выражения вашего алгоритма, чем конечный автомат, то его естественное выражение в коде включает неструктурированные ветви, и с этим ничего не поделаешь, что не делает структуру сам по себе государственный аппарат более неясный, а не менее.
Авторы компиляторов знают это, поэтому исходный код для большинства компиляторов, реализующих парсеры LALR *, содержит gotos. Однако очень немногие люди когда-либо будут кодировать свои лексические анализаторы и парсеры.
* - IIRC, возможно реализовать грамматику LALL полностью рекурсивным спуском, не прибегая к таблицам переходов или другим неструктурированным операторам управления, так что если вы действительно противозаконны, это один выход.
Теперь следующий вопрос: «Является ли этот пример одним из таких случаев?»
При просмотре я вижу, что у вас есть три возможных следующих состояния в зависимости от обработки текущего состояния. Поскольку один из них («по умолчанию») - это просто строка кода, технически вы можете избавиться от него, добавив эту строку кода в конец состояний, к которым он применяется. Это привело бы вас к двум возможным следующим состояниям.
Один из оставшихся («Три») разветвляется только от одного места, которое я вижу. Таким образом, вы могли бы избавиться от этого таким же образом. В итоге вы получите код, который выглядит следующим образом:
Однако, опять же, это был игрушечный пример, который вы предоставили. В случаях, когда «default» содержит нетривиальный объем кода, «три» переводится из нескольких состояний или (что наиболее важно) дальнейшее сопровождение может добавить или усложнить состояния , вам, честно говоря, будет лучше использование gotos (и, возможно, даже избавление от структуры enum-case, которая скрывает сущность конечного автомата вещей, если только для этого нет какой-либо веской причины).
источник
goto
.Лучший ответ - использовать полиморфизм .
Другой ответ, который, IMO, делает вещи более понятными и, возможно, короче :
goto
вероятно, мой 58-й выбор здесь ...источник
Почему бы не это:
Хорошо, я согласен, это хакерство (кстати, я не разработчик C #, поэтому извините за код), но с точки зрения эффективности это обязательно? Использование перечислений в качестве индекса массива является допустимым C #.
источник
int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };
?Если вы не можете или не хотите использовать флаги, используйте хвостовую рекурсивную функцию. В 64-битном режиме выпуска компилятор будет генерировать код, который очень похож на ваш
goto
оператор. Вы просто не должны иметь дело с этим.источник
Принятое решение прекрасно и является конкретным решением вашей проблемы. Однако я хотел бы предложить альтернативное, более абстрактное решение.
По моему опыту, использование перечислений для определения потока логики - это запах кода, поскольку это часто признак плохого дизайна класса.
Я столкнулся с реальным примером этого в коде, над которым я работал в прошлом году. Первоначальный разработчик создал один класс, который выполнял логику импорта и экспорта и переключался между ними на основе перечисления. Теперь код был похож и имел некоторый повторяющийся код, но он отличался настолько, что это сделало код значительно более сложным для чтения и практически невозможным для тестирования. В итоге я реорганизовал это в два отдельных класса, которые упростили оба и фактически позволили мне обнаружить и устранить ряд незарегистрированных ошибок.
Еще раз, я должен сказать, что использование перечислений для управления потоком логики часто является проблемой проектирования. В общем случае, Enums должны использоваться главным образом для обеспечения безопасных для типов, удобных для потребителя значений, где возможные значения четко определены. Их лучше использовать в качестве свойства (например, в качестве идентификатора столбца в таблице), чем в качестве механизма логического управления.
Давайте рассмотрим проблему, представленную в вопросе. Я действительно не знаю контекст здесь, или что представляет это перечисление. Это рисование карт? Рисование картин? Рисовать кровь? Важен ли порядок? Я также не знаю, насколько важна производительность. Если производительность или память имеют решающее значение, то это решение, вероятно, не будет тем, которое вам нужно.
В любом случае, давайте рассмотрим перечисление:
Здесь мы имеем ряд различных значений enum, представляющих разные бизнес-концепции.
Вместо этого мы могли бы использовать абстракции для упрощения вещей.
Давайте рассмотрим следующий интерфейс:
Затем мы можем реализовать это как абстрактный класс:
У нас может быть конкретный класс для представления чертежей один, два и три (которые ради аргумента имеют разную логику). Они могут потенциально использовать базовый класс, определенный выше, но я предполагаю, что концепция DrawOne отличается от концепции, представленной enum:
И теперь у нас есть три отдельных класса, которые могут быть составлены, чтобы обеспечить логику для других классов.
Этот подход гораздо более многословен. Но у этого есть преимущества.
Рассмотрим следующий класс, который содержит ошибку:
Насколько проще обнаружить отсутствующий вызов drawThree.Draw ()? И, если порядок важен, порядок вызовов при розыгрыше также очень легко увидеть и проследить.
Недостатки этого подхода:
Преимущества этого подхода:
Рассматривайте этот (или аналогичный) подход всякий раз, когда вы чувствуете необходимость иметь какой-либо сложный логический управляющий код, записанный в операторах case. Будущее, вы будете счастливы, что сделали.
источник
Если вы намереваетесь использовать здесь переключатель, ваш код будет на самом деле быстрее, если вы будете обрабатывать каждый случай отдельно
в каждом случае выполняется только одна арифметическая операция
также, как другие заявили, если вы планируете использовать gotos, вам, вероятно, следует переосмыслить свой алгоритм (хотя я признаю, что отсутствие падений в C #, хотя и может быть причиной для использования goto). См. Знаменитую статью Эдгара Дейкстры «Перейти к заявлению, которое считается вредным»
источник
Для вашего конкретного примера, поскольку все, что вы действительно хотели получить из перечисления, это указатель «делать / не делать» для каждого из шагов, решение, которое переписывает ваши три
if
утверждения, предпочтительнее, чем «a»switch
, и хорошо, что вы сделали его принятым ответом. ,Но если у вас была какая-то более сложная логика, которая не сработала так чисто, то я все же нахожу
goto
вswitch
утверждениях смущение. Я бы предпочел увидеть что-то вроде этого:Это не идеально, но я думаю, что так лучше, чем с
goto
s. Если последовательности событий настолько длинные и настолько дублируют друг друга, что на самом деле не имеет смысла прописывать полную последовательность для каждого случая, я бы предпочел подпрограмму, а неgoto
для уменьшения дублирования кода.источник
Причиной ненависти этого
goto
ключевого слова является кодК сожалению! Это явно не сработает.
message
Переменная не определена в этом пути кода. Так что C # не пройдет этого. Но это может быть неявным. Рассмотреть возможностьИ предположим, что
display
тогда в нем естьWriteLine
утверждение.Этот тип ошибки может быть трудно найти, потому что
goto
затеняет путь к коду.Это упрощенный пример. Предположим, что реальный пример не будет столь же очевидным. Там может быть пятьдесят строк кода между
label:
и использованиемmessage
.Язык может помочь исправить это, ограничивая
goto
использование, только спускаясь из блоков. Но C #goto
не ограничен этим. Он может перепрыгивать через код. Кроме того, если вы собираетесь ограничитьgoto
, это также хорошо, чтобы изменить имя. Другие языки используютbreak
для спуска из блоков, либо с номером (из блоков для выхода), либо с меткой (на одном из блоков).Концепция
goto
низкоуровневого обучения машинному языку. Но вся причина, по которой у нас языки более высокого уровня, заключается в том, что мы ограничены абстракциями более высокого уровня, например, переменной областью действия.Все это говорит о том, что если вы используете C #
goto
внутриswitch
оператора для перехода от случая к случаю, это достаточно безопасно. Каждый случай уже является точкой входа. Я все еще думаю, что называть этоgoto
глупо в этой ситуации, так как это связывает это безвредное использованиеgoto
с более опасными формами. Я бы предпочел, чтобы они использовали что-то подобноеcontinue
для этого. Но по какой-то причине они забыли спросить меня, прежде чем они написали язык.источник
C#
языке вы не можете перепрыгивать через объявления переменных. Для доказательства запустите консольное приложение и введите код, который вы указали в своем сообщении. Вы получите ошибку компилятора в вашей метке, утверждающую, что ошибка заключается в использовании неназначенной локальной переменной 'message' . На языках, где это разрешено, это актуально, но не на языке C #.Когда у вас есть много вариантов (и даже больше, как вы говорите), тогда, возможно, это не код, а данные.
Создайте словарь, отображающий значения перечисления в действия, выраженные либо в виде функций, либо в виде более простого типа перечисления, представляющего действия Затем ваш код можно свести к простому поиску по словарю, после чего следует либо вызвать значение функции, либо переключиться на упрощенный выбор.
источник
Используйте цикл и разницу между разрывом и продолжением.
источник