Уникати вуду "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 акуратно вирішує цей варіант, навіть якщо ваш приклад заяви не був прийнятним і мені це добре виглядає
Еван

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

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

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

11
Використання "goto case" в C # відрізняється від використання більш загального "goto", оскільки саме так ви досягаєте явного переходу.
Hammerite

Відповіді:


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 або пізнішої версії.
користувач1118321

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типу замість оголошення an enum.
BJ Майєрс

136

Основна проблема IMO полягає в тому, що цей фрагмент коду навіть не повинен існувати.

Ви, мабуть, маєте три незалежні умови та три самостійні дії, які слід здійснити, якщо ці умови є істинними. То чому ж все це об'єднує в один фрагмент коду, який потребує трьох булевих прапорів, щоб сказати, що робити (чи ти їх придушуєш чи перетворюєшся на перелік), а потім робиш комбінацію трьох незалежних речей? Принцип єдиної відповідальності, здається, не має тут робочого дня.

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

Якщо б було десять прапорів і дій, а не три, ви розширили б такий код на обробку 1024 різних комбінацій? Я сподіваюся, що не! Якщо 1024 занадто багато, 8 також занадто багато, з тієї ж причини.


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

9
Відкликав цю відповідь. Якщо ви просто хочете обговорити GOTO, це питання інше, ніж питання, яке ви задали. Виходячи з поданих доказів, у вас є три суперечливі вимоги, які не слід узагальнювати. З точки зору SE, я вважаю, що правильна відповідь алефзеро.

8
@Deduplicator Один погляд на цю мішану частину деяких, але не всіх комбінацій 1,2 та 3 показує, що це не "добре розроблений API".
user949300

4
Стільки цього. Інші відповіді насправді не покращують питання, про які йдеться. Цей код потребує переписування. Немає нічого поганого в написанні додаткових функцій.
only_pro

3
Я люблю цю відповідь, особливо "Якщо 1024 занадто багато, 8 теж занадто багато". Але це по суті "використовувати поліморфізм", чи не так? І моя (слабша) відповідь отримує багато лайно, щоб сказати це. Чи можу я взяти на роботу вашу PR-особу? :-)
user949300

29

Ніколи не використовуйте готос - одна з концепцій інформатики "брехні для дітей" . Це правильна порада у 99% часу, а часи, які вони не такі, є настільки рідкісними та спеціалізованими, що набагато краще для всіх, якщо це просто пояснено новим кодерам як "не використовуйте їх".

Тому , коли слід їх використовувати? Є кілька сценаріїв , але основні один , здається вам ударяти це: коли ви кодування державної машини . Якщо немає кращого організованого та структурованого вираження вашого алгоритму, ніж державна машина, то його природне вираження в коді включає неструктуровані гілки, і не так багато можливо зробити, що не може скласти структуру сама державна машина більш незрозуміла, ніж менша.

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

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


Тепер наступне питання: "Чи є цей приклад одним із таких випадків?"

Я бачу, що я переглядаю це, що у вас є три різні можливі наступні стани, залежно від обробки поточного стану. Оскільки одна з них ("за замовчуванням") - це лише рядок коду, технічно ви можете позбутися від неї, позначивши цей рядок коду в кінці станів, до яких він застосовується. Це дозволить вам перейти до двох можливих наступних держав.

Один із решти ("три") розгалужений лише з одного місця, яке я бачу. Таким чином ви могли позбутися від нього аналогічним чином. Ви закінчите з таким кодом:

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;
}

Однак знову це був іграшковий приклад, який ви надали. У тих випадках, коли "за замовчуванням" містить нетривіальну кількість коду, "три" переходить у декілька станів, або (що найважливіше) подальше обслуговування, можливо, додасть або ускладнить стану , ви, чесно, вам краще використання готосу (і, можливо, навіть позбавлення від структури перешкод, яка приховує стан машинного характеру речей, якщо тільки немає певних вагомих причин, для яких це потрібно залишитися).


2
@PerpetualJ - Ну, я, звичайно, рада, що ти знайшов щось чистіше. Можливо, все-таки буде цікаво, якщо це справді ваша ситуація, спробувати це з gotos (не випадок і перерахунок. Просто позначені блоки та gotos) і подивитися, як вони порівнюються в читанні. Незважаючи на це, варіант "goto" не повинен бути просто читабельнішим, але достатньо читабельним, щоб відмовитися від аргументів і спроб "виправити", що передаються шинкою, від інших розробників, тому, мабуть, ви знайшли найкращий варіант.
ТЕД

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

5
@ user949300 - Не практикуйте проти створення державних машин, це не так. Ви можете просто прочитати мій попередній коментар до цієї відповіді на прикладі реального світу. Якщо ви спробуєте використати випадкові наскрізні випадки C, які накладають штучну структуру (їх лінійний порядок) на алгоритм станкової машини, який не має в собі такого обмеження, ви опинитесь з геометрично зростаючою складністю кожного разу, коли вам доведеться переставляти всі свої замовлення для розміщення нової держави. Якщо ви просто кодуєте стани та переходи, як алгоритм вимагає, цього не відбувається. Нові держави тривіально додати.
ТЕД

8
@ user949300 - Знову ж таки, "~ 40 років практики" побудови компіляторів показує, що gotos - це насправді найкращий спосіб для частин цього проблемного домену, і саме тому, якщо подивитися на вихідний код компілятора, ви майже завжди знайдете gotos.
ТЕД

3
@ user949300 Код спагетті не просто по суті з'являється під час використання конкретних функцій чи умов. Він може з’являтися будь-якою мовою, функцією чи умовою в будь-який час. Причиною є використання зазначеної мови, функції чи конвенції у програмі, якої вона не була призначена, і змушення її згинати назад, щоб витягнути її. Завжди використовуйте правильний інструмент для роботи, і так, іноді це означає використовувати goto.
Abion47

26

Найкраща відповідь - використовувати поліморфізм .

Ще одна відповідь, яка, IMO, робить if речі яснішими та, мабуть, коротшими :

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
Я фактично робив магістерську дисертацію щодо використання поліморфізму виконання, щоб позбутися готосу в державних машинах компіляторів. Це цілком можливо, але я з тих пір виявив, що на практиці це не варто докладати зусиль у більшості випадків. Він вимагає тонни налаштування OO, і все це, звичайно, може закінчитися помилками (і ця відповідь не містить).
ТЕД

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 # btw, тож вибачте мене за код), але з точки зору ефективності це необхідно? Використання enums як індексу масиву є дійсним 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 Я не думаю, що мені потрібно робити це так, але чудово для майбутніх читачів!
ВічнийJ

2

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

На мій досвід, використання перерахунків для визначення потоку логіки є кодовим запахом, оскільки це часто ознака поганого дизайну класів.

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

Ще раз зазначу, що використання переписок для управління потоком логіки часто є проблемою дизайну. У загальному випадку Enums слід застосовувати здебільшого для забезпечення безпечних для споживача типів цін, де можливі значення чітко визначені. Їх краще використовувати як властивість (наприклад, як ідентифікатор стовпця в таблиці), ніж як механізм управління логікою.

Розглянемо проблему, представлену у питанні. Я насправді не знаю контексту, або що представляє цей перелік. Це малювання карт? Малювання картинок? Малювання крові? Чи важливий порядок? Я також не знаю, наскільки важлива робота. Якщо продуктивність чи пам’ять є критичними, то це рішення, ймовірно, не буде таким, яким ви хочете.

У будь-якому випадку розглянемо перелік:

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

Тут ми маємо декілька різних цінностей, які представляють різні бізнес-концепції.

Ми могли б використати замість цього абстракції для спрощення речей.

Розглянемо наступний інтерфейс:

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 досить просто

Розгляньте цей (або подібний) підхід, коли ви відчуваєте необхідність будь-якого складного логічного керуючого коду, записаного у виписки справи. Майбутнє ви будете щасливі, що зробили.


Це дуже добре написана відповідь, і я цілком згоден з підходом. У моєму конкретному випадку щось таке багатослівне було б надмірно нескінченним, враховуючи тривіальний код за кожним. Щоб поставити це в перспективі, уявіть, що код просто виконує консоль.WriteLine (n). Це практично еквівалент того, що робив код, з яким я працював. У 90% всіх інших випадків ваше рішення, безумовно, є найкращою відповіддю при дотриманні OOP.
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 # s не випадає регістр, хоча це може бути приводом для використання goto). Дивіться відомий документ Едгара Дайкстри "Перейти до заяви, що вважається шкідливим"


3
Я б сказав, що це чудова ідея для того, хто стикається з більш простим питанням, тому дякую, що ви розмістили її. У моєму випадку це не так просто.
PerpetualJ

Крім того, у нас сьогодні точно немає проблем, які були у Едгара під час написання документа; Більшість людей в наш час навіть не мають поважних причин ненавидіти Goto вбік від нього, робить код важким для читання. Ну, чесно кажучи, якщо його зловживають, то так, інакше він потрапляє в метод, що містить, так що ви дійсно не можете зробити код спагетті. Навіщо витрачати зусилля, щоб обійти особливості мови? Я б сказав, що гото є архаїчним і що ви повинні думати про інші рішення у 99% випадків використання; але якщо вам це потрібно, саме для цього і потрібно.
PerpetualJ

Це не буде добре масштабуватися, коли є багато булів.
user949300

0

Для вашого конкретного прикладу, оскільки все, що ви справді хотіли від перерахунку, - це показник "робити / робити не робити" для кожного з кроків, рішення, яке переписує три ваші ifтвердження, є кращим для "a" switch, і добре, що ви зробили це прийнятою відповіддю .

Але якщо у вас була якась більш складна логіка, яка не спрацювала так чисто, то я все-таки вважаю, що gotos у 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.