Избегать "goto" вуду?


47

У меня есть 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ключевым словом связана стигма, которую я хотел бы избежать. Я уверен, что должен быть лучший способ убрать это.


Мой вопрос

Есть ли лучший способ справиться с этим конкретным случаем, не влияя на читаемость и ремонтопригодность?


28
Похоже, вы хотите провести большую дискуссию. Но вам понадобится лучший пример хорошего случая, чтобы использовать goto. flag enum аккуратно решает эту проблему, даже если ваш пример выражения if был неприемлем, и он мне
подходит

5
@ Иван Никто не использует goto. Когда вы в последний раз просматривали исходный код ядра Linux?
Джон Дума

6
Вы используете, gotoкогда структура высокого уровня не существует на вашем языке. Я иногда хотел бы, чтобы был fallthru; ключевое слово, чтобы избавиться от этого конкретного использования, gotoну да ладно.
Джошуа

25
В общих чертах, если вы чувствуете необходимость использовать язык gotoвысокого уровня, такой как C #, то вы, вероятно, упустили множество других (и лучших) вариантов проектирования и / или реализации. Очень обескуражен.
code_dredd

11
Использование "goto case" в C # отличается от использования более общего "goto", потому что это то, как вы выполняете явное падение.
Хаммерите

Ответы:


175

Я нахожу код трудно читать с gotoзаявлениями. Я бы порекомендовал структурировать ваш по- enumдругому. Например, если вы enumпредставляли собой битовое поле, где каждый бит представлял один из вариантов, это могло бы выглядеть так:

[Flags]
public enum ExampleEnum {
    One = 0b0001,
    Two = 0b0010,
    Three = 0b0100
};

Атрибут Flags сообщает компилятору, что вы устанавливаете значения, которые не перекрываются. Код, который вызывает этот код, может установить соответствующий бит. Затем вы можете сделать что-то вроде этого, чтобы прояснить, что происходит:

if (myEnum.HasFlag(ExampleEnum.One))
{
    CallOne();
}
if (myEnum.HasFlag(ExampleEnum.Two))
{
    CallTwo();
}
if (myEnum.HasFlag(ExampleEnum.Three))
{
    CallThree();
}

Для этого требуется код, который устанавливает myEnumправильные битовые поля и помечается атрибутом Flags. Но вы можете сделать это, изменив значения перечислений в вашем примере на:

[Flags]
public enum ExampleEnum {
    One = 0b0001,
    Two = 0b0010,
    Three = 0b0100,
    OneAndTwo = One | Two,
    OneAndThree = One | Three,
    TwoAndThree = Two | Three
};

Когда вы пишете число в форме 0bxxxx, вы указываете его в двоичном виде. Итак, вы можете видеть, что мы установили бит 1, 2 или 3 (ну, технически 0, 1 или 2, но вы поняли идею). Вы также можете называть комбинации с помощью побитового ИЛИ, если комбинации могут часто устанавливаться вместе.


5
Это кажется так ! Но это должно быть C # 7.0 или более поздняя версия.
user1118321

31
Во всяком случае, можно было бы также сделать это следующим образом : public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };. Не нужно настаивать на C # 7 +.
Дедупликатор

8
Вы можете использовать Enum.HasFlag
IMil

13
[Flags]Атрибут ничего компилятору не сигнализировать. Вот почему вы все еще должны явно объявить значения перечисления как степени 2.
Джо Сьюэлл

19
@UKMonkey Я категорически не согласен. HasFlagявляется описательным и явным термином для выполняемой операции и абстрагирует реализацию от функциональности. Использование, &потому что оно распространено в других языках, не имеет больше смысла, чем использование byteтипа вместо объявления enum.
Би Джей Майерс

136

IMO корень проблемы в том, что этот кусок кода даже не должен существовать.

Очевидно, у вас есть три независимых условия и три независимых действия, которые необходимо выполнить, если эти условия выполняются. Так почему же все это вкладывается в один кусок кода, которому нужны три логических флага, чтобы указать ему, что делать (независимо от того, запутываете ли вы их в перечисление), и затем происходит некоторая комбинация из трех независимых вещей? Принцип единой ответственности, похоже, имеет выходной день здесь.

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

Если бы было десять флагов и действий не три, вы бы расширили этот вид кода для обработки 1024 различных комбинаций? Надеюсь нет! Если 1024 слишком много, 8 тоже слишком много по той же причине.


10
На самом деле, есть много хорошо разработанных API, которые имеют всевозможные флаги, опции и другие параметры. Некоторые могут быть переданы, некоторые имеют прямые последствия, а третьи могут быть проигнорированы, не нарушая SRP. Во всяком случае, функция может даже декодировать какой-то внешний ввод, кто знает? Кроме того, сокращение до абсурда часто приводит к абсурдным результатам, особенно если отправная точка даже не настолько прочна.
Дедупликатор

9
Проголосовал этот ответ. Если вы просто хотите обсудить GOTO, это другой вопрос, чем тот, который вы задали. На основании представленных доказательств у вас есть три непересекающихся требования, которые не следует объединять. С точки зрения SE, я утверждаю, что alephzero - правильный ответ.

8
@Deduplicator Один взгляд на эту путаницу некоторых, но не всех комбинаций 1,2 и 3 показывает, что это не «хорошо разработанный API».
user949300

4
Вот так много. Другие ответы не улучшают то, что в вопросе. Этот код нуждается в переписывании. Нет ничего плохого в том, чтобы писать больше функций.
Извиниться и восстановить Монику

3
Мне нравится этот ответ, особенно «Если 1024 слишком много, 8 тоже слишком много». Но это по сути "использование полиморфизма", не так ли? И мой (более слабый) ответ получает много дерьма за это. Могу ли я нанять вашего пиарщика? :-)
user949300

29

Никогда не используйте gotos - это одна из концепций информатики "лгать детям" . Это правильный совет в 99% случаев, а иногда это не так редко и специализировано, что гораздо лучше для всех, если его просто объяснить новым кодировщикам как «не используйте их».

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

Авторы компиляторов знают это, поэтому исходный код для большинства компиляторов, реализующих парсеры LALR *, содержит gotos. Однако очень немногие люди когда-либо будут кодировать свои лексические анализаторы и парсеры.

* - IIRC, возможно реализовать грамматику LALL полностью рекурсивным спуском, не прибегая к таблицам переходов или другим неструктурированным операторам управления, так что если вы действительно противозаконны, это один выход.


Теперь следующий вопрос: «Является ли этот пример одним из таких случаев?»

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

Один из оставшихся («Три») разветвляется только от одного места, которое я вижу. Таким образом, вы могли бы избавиться от этого таким же образом. В итоге вы получите код, который выглядит следующим образом:

switch (exampleValue) {
    case OneAndTwo: i += 3 break;
    case OneAndThree: i += 4 break;
    case Two: i += 2 break;
    case TwoAndThree: i += 5 break;
    case Three: i += 3 break;
    default: i++ break;
}

Однако, опять же, это был игрушечный пример, который вы предоставили. В случаях, когда «default» содержит нетривиальный объем кода, «три» переводится из нескольких состояний или (что наиболее важно) дальнейшее сопровождение может добавить или усложнить состояния , вам, честно говоря, будет лучше использование gotos (и, возможно, даже избавление от структуры enum-case, которая скрывает сущность конечного автомата вещей, если только для этого нет какой-либо веской причины).


2
@PerpetualJ - Ну, я, конечно, рад, что ты нашел что-то чище. Возможно, все еще будет интересно, если это действительно ваша ситуация, попробовать это с gotos (без case или enum. Просто помеченные блоки и gotos) и посмотреть, как они сравниваются в удобочитаемости. При этом опция «goto» должна быть не только более удобочитаемой, но и более читаемой, чтобы не допускать аргументов и попыток «исправить» других разработчиков, так что, скорее всего, вы нашли лучший вариант.
TED

4
Я не верю, что вам "лучше использовать gotos", если "дальнейшее обслуживание может добавить или усложнить состояния". Вы действительно утверждаете, что спагетти-код легче поддерживать, чем структурированный код? Это противоречит ~ 40 годам практики.
user949300

5
@ user949300 - Это не практика против создания конечных автоматов. Вы можете просто прочитать мой предыдущий комментарий к этому ответу для примера из реальной жизни. Если вы попытаетесь использовать аварийные ситуации в C, которые накладывают искусственную структуру (их линейный порядок) на алгоритм конечного автомата, который по сути не имеет такого ограничения, вы будете сталкиваться с геометрически возрастающей сложностью каждый раз, когда вам придется переставлять все свои заказы для размещения нового государства. Если вы просто кодируете состояния и переходы, как требует алгоритм, этого не произойдет. Новые состояния тривиально добавить.
TED

8
@ user949300 - Опять же, "~ 40-летний опыт" создания компиляторов показывает, что gotos - действительно лучший способ для частей этой проблемной области, поэтому, если вы посмотрите на исходный код компилятора, вы почти всегда найдете gotos.
TED

3
@ user949300 Спагетти-код не просто присутствует при использовании определенных функций или соглашений. Он может появиться на любом языке, функции или соглашении в любое время. Причиной является использование указанного языка, функции или соглашения в приложении, для которого оно не было предназначено, и заставление его наклоняться назад, чтобы выполнить его. Всегда используйте правильный инструмент для работы, и да, иногда это означает использование goto.
Abion47

26

Лучший ответ - использовать полиморфизм .

Другой ответ, который, IMO, делает вещи более понятными и, возможно, короче :

if (One || OneAndTwo || OneAndThree)
  CallOne();
if (Two || OneAndTwo || TwoAndThree)
  CallTwo();
if (Three || OneAndThree || TwoAndThree)
  CallThree();

goto вероятно, мой 58-й выбор здесь ...


5
+1 за предложение полиморфизма, хотя в идеале это могло бы упростить клиентский код до просто «Call ();», используя Tell not ask - оттолкнуть логику принятия решений от потребляющего клиента в механизм и иерархию классов.
Эрик Эйдт

12
Провозглашать что-либо лучшим невидимым зрением, безусловно, очень смело Но без дополнительной информации я предлагаю немного скептицизма и открытости. Возможно, он страдает от комбинаторного взрыва?
Дедупликатор

Ух ты, я думаю, это первый раз, когда за меня отказались от предположения о полиморфизме. :-)
user949300

25
Некоторые люди, столкнувшись с проблемой, говорят: «Я просто использую полиморфизм». Теперь у них две проблемы и полиморфизм.
Легкость гонки с Моникой

8
Я фактически защитил магистерскую диссертацию об использовании полиморфизма во время выполнения, чтобы избавиться от gotos в конечных автоматах компиляторов. Это вполне выполнимо, но с тех пор я обнаружил, что на практике это не стоит усилий в большинстве случаев. Требуется тонна кода OO-setup, и все это, конечно, может привести к ошибкам (и которые этот ответ опускает).
TED

10

Почему бы не это:

public enum ExampleEnum {
    One = 0, // Why not?
    OneAndTwo,
    OneAndThree,
    Two,
    TwoAndThree,
    Three
}
int[] COUNTS = { 1, 3, 4, 2, 5, 3 }; // Whatever

int ComputeExampleValue(int i, ExampleEnum exampleValue) {
    return i + COUNTS[(int)exampleValue];
}

Хорошо, я согласен, это хакерство (кстати, я не разработчик C #, поэтому извините за код), но с точки зрения эффективности это обязательно? Использование перечислений в качестве индекса массива является допустимым C #.


3
Да. Еще один пример замены кода данными (что обычно желательно для скорости, структуры и удобства обслуживания).
Питер - Восстановить Монику

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

У меня нет доступа к # компилятор C, но , возможно , код может быть более безопасным , используя этот вид кода: int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };?
Лоран Грегуар

7

Если вы не можете или не хотите использовать флаги, используйте хвостовую рекурсивную функцию. В 64-битном режиме выпуска компилятор будет генерировать код, который очень похож на ваш gotoоператор. Вы просто не должны иметь дело с этим.

int ComputeExampleValue(int i, ExampleEnum exampleValue) {
    switch (exampleValue) {
        case One: return i + 1;
        case OneAndTwo: return ComputeExampleValue(i + 2, ExampleEnum.One);
        case OneAndThree: return ComputeExampleValue(i + 3, ExampleEnum.One);
        case Two: return i + 2;
        case TwoAndThree: return ComputeExampleValue(i + 2, ExampleEnum.Three);
        case Three: return i + 3;
   }
}

Это уникальное решение! +1 Я не думаю, что мне нужно делать это таким образом, но отлично подходит для будущих читателей!
PerpetualJ

2

Принятое решение прекрасно и является конкретным решением вашей проблемы. Однако я хотел бы предложить альтернативное, более абстрактное решение.

По моему опыту, использование перечислений для определения потока логики - это запах кода, поскольку это часто признак плохого дизайна класса.

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

Еще раз, я должен сказать, что использование перечислений для управления потоком логики часто является проблемой проектирования. В общем случае, Enums должны использоваться главным образом для обеспечения безопасных для типов, удобных для потребителя значений, где возможные значения четко определены. Их лучше использовать в качестве свойства (например, в качестве идентификатора столбца в таблице), чем в качестве механизма логического управления.

Давайте рассмотрим проблему, представленную в вопросе. Я действительно не знаю контекст здесь, или что представляет это перечисление. Это рисование карт? Рисование картин? Рисовать кровь? Важен ли порядок? Я также не знаю, насколько важна производительность. Если производительность или память имеют решающее значение, то это решение, вероятно, не будет тем, которое вам нужно.

В любом случае, давайте рассмотрим перечисление:

// All possible combinations of One - Eight.
public enum ExampleEnum {
    One,
    Two,
    TwoOne,
    Three,
    ThreeOne,
    ThreeTwo,
    ThreeOneTwo
}

Здесь мы имеем ряд различных значений enum, представляющих разные бизнес-концепции.

Вместо этого мы могли бы использовать абстракции для упрощения вещей.

Давайте рассмотрим следующий интерфейс:

public interface IExample
{
  void Draw();
}

Затем мы можем реализовать это как абстрактный класс:

public abstract class ExampleClassBase : IExample
{
  public abstract void Draw();
  // other common functionality defined here
}

У нас может быть конкретный класс для представления чертежей один, два и три (которые ради аргумента имеют разную логику). Они могут потенциально использовать базовый класс, определенный выше, но я предполагаю, что концепция DrawOne отличается от концепции, представленной enum:

public class DrawOne
{
  public void Draw()
  {
    // Drawing logic here
  }
}

public class DrawTwo
{
  public void Draw()
  {
    // Drawing two logic here
  }
}

public class DrawThree
{
  public void Draw()
  {
    // Drawing three logic here
  }
}

И теперь у нас есть три отдельных класса, которые могут быть составлены, чтобы обеспечить логику для других классов.

public class One : ExampleClassBase
{
  private DrawOne drawOne;

  public One(DrawOne drawOne)
  {
    this.drawOne = drawOne;
  }

  public void Draw()
  {
    this.drawOne.Draw();
  }
}

public class TwoOne : ExampleClassBase
{
  private DrawOne drawOne;
  private DrawTwo drawTwo;

  public One(DrawOne drawOne, DrawTwo drawTwo)
  {
    this.drawOne = drawOne;
    this.drawTwo = drawTwo;
  }

  public void Draw()
  {
    this.drawOne.Draw();
    this.drawTwo.Draw();
  }
}

// the other six classes here

Этот подход гораздо более многословен. Но у этого есть преимущества.

Рассмотрим следующий класс, который содержит ошибку:

public class ThreeTwoOne : ExampleClassBase
{
  private DrawOne drawOne;
  private DrawTwo drawTwo;
  private DrawThree drawThree;

  public One(DrawOne drawOne, DrawTwo drawTwo, DrawThree drawThree)
  {
    this.drawOne = drawOne;
    this.drawTwo = drawTwo;
    this.drawThree = drawThree;
  }

  public void Draw()
  {
    this.drawOne.Draw();
    this.drawTwo.Draw();
  }
}

Насколько проще обнаружить отсутствующий вызов drawThree.Draw ()? И, если порядок важен, порядок вызовов при розыгрыше также очень легко увидеть и проследить.

Недостатки этого подхода:

  • Каждый из восьми представленных вариантов требует отдельного класса
  • Это будет использовать больше памяти
  • Это сделает ваш код внешне больше
  • Иногда такой подход невозможен, хотя возможны некоторые отклонения от него.

Преимущества этого подхода:

  • Каждый из этих классов полностью тестируемый; потому что
  • Цикломатическая сложность методов рисования низкая (и теоретически я мог бы при необходимости высмеивать классы DrawOne, DrawTwo или DrawThree)
  • Методы розыгрыша понятны - разработчик не должен связывать свой мозг узлами, выясняя, что делает метод
  • Ошибки легко обнаружить и сложно написать
  • Классы объединяются в классы более высокого уровня, что означает, что определение класса ThreeThreeThree легко

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


Это очень хорошо написанный ответ, и я полностью согласен с подходом. В моем конкретном случае что-то такое многословное было бы излишним до бесконечности, учитывая тривиальный код каждого из них. Чтобы представить это в перспективе, представьте, что код просто выполняет Console.WriteLine (n). Это практически эквивалентно тому, что делал код, с которым я работал. В 90% всех других случаев ваше решение определенно является лучшим ответом при ООП.
PerpetualJ

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

Я полностью согласен с этим, именно поэтому я оказался здесь, потому что пытался решить проблему масштабируемости с помощью чего-то, что другой разработчик написал по привычке.
PerpetualJ

0

Если вы намереваетесь использовать здесь переключатель, ваш код будет на самом деле быстрее, если вы будете обрабатывать каждый случай отдельно

switch(exampleValue)
{
    case One:
        i++;
        break;
    case Two:
        i += 2;
        break;
    case OneAndTwo:
    case Three:
        i+=3;
        break;
    case OneAndThree:
        i+=4;
        break;
    case TwoAndThree:
        i+=5;
        break;
}

в каждом случае выполняется только одна арифметическая операция

также, как другие заявили, если вы планируете использовать gotos, вам, вероятно, следует переосмыслить свой алгоритм (хотя я признаю, что отсутствие падений в C #, хотя и может быть причиной для использования goto). См. Знаменитую статью Эдгара Дейкстры «Перейти к заявлению, которое считается вредным»


3
Я бы сказал, что это отличная идея для тех, кто столкнулся с более простым вопросом, поэтому спасибо за публикацию. В моем случае это не так просто.
PerpetualJ

Кроме того, у нас сегодня точно нет проблем, которые были у Эдгара во время написания его статьи; у большинства людей в настоящее время нет даже веской причины ненавидеть goto, кроме того, что код трудно читать. Что ж, если честно, если этим злоупотребляют, тогда да, в противном случае он попадает в ловушку содержащего метода, так что вы не сможете действительно создать код для спагетти. Зачем тратить усилия, чтобы обойти особенность языка? Я бы сказал, что goto является архаичным, и что вы должны думать о других решениях в 99% случаев; но если вам это нужно, вот для чего оно.
PerpetualJ

Это не будет хорошо масштабироваться, когда есть много логических значений.
user949300

0

Для вашего конкретного примера, поскольку все, что вы действительно хотели получить из перечисления, это указатель «делать / не делать» для каждого из шагов, решение, которое переписывает ваши три ifутверждения, предпочтительнее, чем «a» switch, и хорошо, что вы сделали его принятым ответом. ,

Но если у вас была какая-то более сложная логика, которая не сработала так чисто, то я все же нахожу gotoв switchутверждениях смущение. Я бы предпочел увидеть что-то вроде этого:

switch (enumVal) {
    case ThreeOneTwo: DrawThree; DrawTwo; DrawOne; break;
    case ThreeTwo:    DrawThree; DrawTwo; break;
    case ThreeOne:    DrawThree; DrawOne; break;
    case TwoOne:      DrawTwo; DrawOne; break;
    case Two:         DrawTwo; break;
    default:          DrawOne; break;
}

Это не идеально, но я думаю, что так лучше, чем с gotos. Если последовательности событий настолько длинные и настолько дублируют друг друга, что на самом деле не имеет смысла прописывать полную последовательность для каждого случая, я бы предпочел подпрограмму, а не gotoдля уменьшения дублирования кода.


0

Я не уверен, что у кого-то действительно есть причина ненавидеть ключевое слово goto в эти дни. Это определенно архаично и не требуется в 99% случаев, но это особенность языка по определенной причине.

Причиной ненависти этого gotoключевого слова является код

if (someCondition) {
    goto label;
}

string message = "Hello World!";

label:
Console.WriteLine(message);

К сожалению! Это явно не сработает. messageПеременная не определена в этом пути кода. Так что C # не пройдет этого. Но это может быть неявным. Рассмотреть возможность

object.setMessage("Hello World!");

label:
object.display();

И предположим, что displayтогда в нем есть WriteLineутверждение.

Этот тип ошибки может быть трудно найти, потому что gotoзатеняет путь к коду.

Это упрощенный пример. Предположим, что реальный пример не будет столь же очевидным. Там может быть пятьдесят строк кода между label:и использованием message.

Язык может помочь исправить это, ограничивая gotoиспользование, только спускаясь из блоков. Но C # gotoне ограничен этим. Он может перепрыгивать через код. Кроме того, если вы собираетесь ограничить goto, это также хорошо, чтобы изменить имя. Другие языки используют breakдля спуска из блоков, либо с номером (из блоков для выхода), либо с меткой (на одном из блоков).

Концепция gotoнизкоуровневого обучения машинному языку. Но вся причина, по которой у нас языки более высокого уровня, заключается в том, что мы ограничены абстракциями более высокого уровня, например, переменной областью действия.

Все это говорит о том, что если вы используете C # gotoвнутри switchоператора для перехода от случая к случаю, это достаточно безопасно. Каждый случай уже является точкой входа. Я все еще думаю, что называть это gotoглупо в этой ситуации, так как это связывает это безвредное использование gotoс более опасными формами. Я бы предпочел, чтобы они использовали что-то подобное continueдля этого. Но по какой-то причине они забыли спросить меня, прежде чем они написали язык.


2
На C#языке вы не можете перепрыгивать через объявления переменных. Для доказательства запустите консольное приложение и введите код, который вы указали в своем сообщении. Вы получите ошибку компилятора в вашей метке, утверждающую, что ошибка заключается в использовании неназначенной локальной переменной 'message' . На языках, где это разрешено, это актуально, но не на языке C #.
PerpetualJ

0

Когда у вас есть много вариантов (и даже больше, как вы говорите), тогда, возможно, это не код, а данные.

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


-7

Используйте цикл и разницу между разрывом и продолжением.

do {
  switch (exampleValue) {
    case OneAndTwo: i += 2; break;
    case OneAndThree: i += 3; break;
    case Two: i += 2; continue;
    case TwoAndThree: i += 2;
    // dropthrough
    case Three: i += 3; continue;
    default: break;
  }
  i++;
} while(0);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.