Переключити перехід заяви на C #?


365

Перехід заяви про перемикання - одна з моїх особистих основних причин любити switchпроти if/else ifконструкцій. Приклад для того, щоб тут:

static string NumberToWords(int number)
{
    string[] numbers = new string[] 
        { "", "one", "two", "three", "four", "five", 
          "six", "seven", "eight", "nine" };
    string[] tens = new string[] 
        { "", "", "twenty", "thirty", "forty", "fifty", 
          "sixty", "seventy", "eighty", "ninety" };
    string[] teens = new string[]
        { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
          "sixteen", "seventeen", "eighteen", "nineteen" };

    string ans = "";
    switch (number.ToString().Length)
    {
        case 3:
            ans += string.Format("{0} hundred and ", numbers[number / 100]);
        case 2:
            int t = (number / 10) % 10;
            if (t == 1)
            {
                ans += teens[number % 10];
                break;
            }
            else if (t > 1)
                ans += string.Format("{0}-", tens[t]);
        case 1:
            int o = number % 10;
            ans += numbers[o];

            break;
        default:
            throw new ArgumentException("number");
    }
    return ans;
}

Розумні люди чіпляються, тому що string[]s повинні бути оголошені поза функцією: ну, вони є, це лише приклад.

Компілятор не працює із наступною помилкою:

Керування не може переходити з однієї мітки справи ('case 3:') на іншу
Керування не може переходити з однієї мітки справи ('case 2:') на іншу

Чому? І чи є такий спосіб отримати таку поведінку, не маючи трьох ifс?

Відповіді:


648

(Скопіюйте / вставте відповідь, яку я надав в іншому місці )

Проникнення switch- caseможна досягти, не маючи коду в case(див. case 0), Або використовуючи спеціальні goto case(див. case 1) Або goto default(див. case 2) Форми:

switch (/*...*/) {
    case 0: // shares the exact same code as case 1
    case 1:
        // do something
        goto case 2;
    case 2:
        // do something else
        goto default;
    default:
        // do something entirely different
        break;
}

121
Я думаю, що в даному конкретному випадку гото не вважається шкідливим.
Томас Оуенс

78
Чорт - я програмував на C # з перших днів 1.0, і я ніколи цього не бачив. Просто йде на показ, ви щодня дізнаєтесь нові речі.
Ерік Форбс

37
Все добре, Еріку. Єдина причина, про яку я знав, це те, що я ботанік теорії компілятора, який читав специфікації ECMA-334 з лупою.
Алекс Лиман

13
@Dancrumb: На момент написання функції C # ще не додав жодних "м'яких" ключових слів (наприклад, "урожайність", "вар", "від" та "вибір"), тому у них було три реальні варіанти: 1 ) зробіть "пропускний" складним ключовим словом (ви не можете використовувати його як ім'я змінної), 2) написати код, необхідний для підтримки такого м'якого ключового слова, 3) використовувати вже зарезервовані ключові слова. №1 була великою проблемою для тих, хто переносить код; №2 було досить великим інженерним завданням, наскільки я розумію; і варіант, з яким вони пішли, №3 мав побічну перевагу: інші розробники, читаючи код після факту, могли дізнатися про особливість з базової концепції goto
Alex Lyman

10
Все це говорить про нові / спеціальні ключові слова для явного прориву. Чи не могли вони просто використати ключове слово "продовжити"? Іншими словами. Перервіться з вимикача або продовжуйте рух до наступного випадку (провалитися).
Тал Евен-Тов

44

"Чому" - це уникати випадкових провалів, за що я вдячний. Це не рідке джерело помилок на C та Java.

Вирішення проблеми полягає у використанні goto, наприклад

switch (number.ToString().Length)
{
    case 3:
        ans += string.Format("{0} hundred and ", numbers[number / 100]);
        goto case 2;
    case 2:
    // Etc
}

Загальний дизайн вимикача / корпусу на мою думку трохи прикро. Він застряг занадто близько до C - є деякі корисні зміни, які можуть бути внесені з точки зору визначення масштабів і т. Д. Можливо, корисніший розумний перемикач, який може зробити узгодження шаблонів і т. Д., Був би корисним, але це дійсно змінюється від перемикання на "перевірити послідовність умов" - в цей момент, можливо, буде закликано інше ім'я.


1
Це різниця між перемикачем і if / elseif в моїй свідомості. Перемикач призначений для перевірки різних станів однієї змінної, тоді як, якщо / elseif можна використовувати для перевірки будь-якої кількості речей, що підключилися, але необов'язково однієї чи тієї ж змінної.
Меттью Шарлі

2
Якби запобігти випадковому падінню, я вважаю, що попередження компілятора було б краще. Так само, як і у вас, якщо у вашої заяви if є завдання:if (result = true) { }
Tal Even-Tov

3
@ TalEven-Tov: Попередження компілятора справді повинні бути у випадках, коли ви майже завжди можете виправити код, щоб бути кращим. Особисто я вважаю за краще неявне зривання, так що це не буде проблемою для початку, але це інша справа.
Джон Скіт

26

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

switch(value)
{
    case 1:// this is still legal
    case 2:
}

23
Я добре ніколи не розумію, чому це не "випадок 1, 2:"
BCS

11
@David Pfeffer: Так, так і є case 1, 2:в мовах, які це дозволяють. Я ніколи не зрозумію, чому будь-яка сучасна мова не хотіла б цього дозволити.
BCS

@BCS із заявою goto, кілька варіантів, розділених комами, можуть бути складними в обробці?
penguat

@pengut: можливо, точніше сказати це case 1, 2: це одна мітка, але з кількома іменами. - FWIW, я вважаю, що більшість мов, які забороняють потрапляння, не є особливим випадком "індикації послідовних справ" індію, а швидше трактують мітки справи як анотацію до наступного висловлювання та вимагають останнього твердження перед заявою, позначеною (одна або більше) мітки справи - це стрибок.
BCS

22

Щоб додати відповіді тут, я вважаю, що варто розглянути протилежне питання разом із цим, а саме. чому C в першу чергу дозволив пропустити?

Будь-яка мова програмування звичайно служить двом цілям:

  1. Надайте інструкції комп’ютеру.
  2. Залиште запис про наміри програміста.

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

І навпаки, чим більше мова виражає наміри програміста, а не засоби, використані для цього, тим зрозуміліша програма як під час написання, так і під час обслуговування.

Тепер, switch його завжди можна було скласти, перетворивши його в еквівалентний ланцюжок if-elseблоків або подібне, але він був розроблений як такий, що дозволяє компілювати в певний загальний шаблон складання, коли людина приймає значення, обчислює зсув з нього (чи то шукаючи таблицю індексується досконалим хешем значення або фактичною арифметикою значення *). На цьому етапі варто відзначити, що сьогодні компіляція C # іноді перетворюється switchна еквівалент if-else, а іноді використовує хеш-підхідний стрибок (а також з C, C ++ та іншими мовами із порівнянним синтаксисом).

У цьому випадку є дві вагомі причини, що дозволяють пропускати:

  1. Так чи інакше буває природно: якщо ви вбудуєте таблицю стрибків у набір інструкцій, і одна з попередніх груп інструкцій не містить певного стрибка чи повернення, то виконання просто природним чином перейде до наступної партії. Дозвіл пропускання - це те, що "просто трапиться", якби ви повернулиswitch використовуючи C на таблицю стрибків за допомогою машинного коду.

  2. Кодери, які писали в асемблері, вже були використані в еквіваленті: коли писати таблицю стрибків вручну в зборі, вони повинні були б врахувати, чи закінчиться даний блок коду поверненням, стрибком поза таблицею, або просто продовжуватиме далі до наступного блоку. Таким чином, надання кодеру додати явне, breakколи це було необхідне, було "природним" і для кодера.

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

Через чотири десятиліття все не зовсім однакове з кількох причин:

  1. Сьогодні кодери на C можуть мати мало досвіду монтажу або взагалі ніякого. Кодери в багатьох інших мовах стилю С є ще рідше (особливо Javascript!). Будь-яке поняття "до чого звикли люди з монтажу" вже не є актуальним.
  2. Поліпшення оптимізації означають, що ймовірність того, що switchвони будуть перетвореніif-else що він оскільки він вважав, що підхід, ймовірно, є найбільш ефективним, або ж перетвориться на особливо езотеричний варіант підходу до стрибків. Відображення між підходами вищого та нижчого рівня не настільки сильне, як раніше.
  3. Досвід показує, що пропускний процес має тенденцію до меншості, а не норми (дослідження компілятора Sun показало, що 3% switchблоків використовували пропуск, крім кількох міток у тому ж блоці, і вважалося, що використання випадок тут означав, що цей 3% був насправді набагато вищий за норму). Таким чином, вивчена мова робить незвичайне більш легким, ніж загальне.
  4. Досвід показує, що пропускне середовище, як правило, є джерелом проблем як у випадках, коли це відбувається випадково, так і у випадках, коли хтось підтримує код, пропускає правильний пропуск. Останнє є тонким доповненням до помилок, пов’язаних із пропусканням, тому що навіть якщо ваш код ідеально відсутній, помилки можуть все-таки спричинити проблеми.

Зв'язані з останніми двома пунктами, розгляньте наступну цитату з поточного видання K&R:

Проникнення від одного випадку до іншого не є надійним, воно схильне до дезінтеграції, коли програма модифікується. За винятком декількох міток для одного обчислення, пропускні дані повинні використовуватися скупо та коментуватися.

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

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

І коли ви думаєте про це, кодуйте так:

switch(x)
{
  case 1:
   foo();
   /* FALLTHRU */
  case 2:
    bar();
    break;
}

Є чи додати що - то , щоб зробити падіння-через явне в коді, це просто не те , що можна виявити (або відсутність яких може бути виявлений) компілятором.

Таким чином, той факт, що він має бути явним із проривом у C #, не додає жодних покарань людям, які добре писали на інших мовах С-стилю, так як вони вже були б явними у своїх проривах. †

Нарешті, використання gotoтут уже є нормою C та інших таких мов:

switch(x)
{
  case 0:
  case 1:
  case 2:
    foo();
    goto below_six;
  case 3:
    bar();
    goto below_six;
  case 4:
    baz();
    /* FALLTHRU */
  case 5:
  below_six:
    qux();
    break;
  default:
    quux();
}

У такому випадку, коли ми хочемо, щоб блок був включений у код, виконаний для значення, відмінного від того, яке приносить один до попереднього блоку, тоді нам вже доведеться використовувати goto. (Звичайно, існують засоби та способи уникнути цього з різними умовами, але це стосується майже всього, що стосується цього питання). Як такий C # побудований на вже звичайному способі вирішення однієї ситуації, коли ми хочемо потрапити на більше ніж один блок коду в a switch, а просто узагальнити його, щоб охопити також провал. Це також зробило обидва випадки зручнішими та самодокументуваннями, оскільки ми маємо додати нову мітку на C, але можемо використовувати caseяк мітку в C #. У C # ми можемо позбутися below_sixетикетки і використовувати, goto case 5що зрозуміліше, що ми робимо. (Ми також повинні були додатиbreakдля default, який я пропустив лише для того, щоб вищезазначений код С явно не був C # кодом).

Таким чином:

  1. C # більше не стосується неоптимізованого виводу компілятора так само безпосередньо, як C-код 40 років тому (а також C цього дня), що робить одне з натхнень провалу неважливим.
  2. C # залишається сумісним з C, не просто маючи на увазі break, для легшого вивчення мови особами, знайомими з подібними мовами, та легшим переносом.
  3. C # видаляє можливе джерело помилок або неправильно зрозумілий код, який добре зафіксований як спричинення проблем протягом останніх чотирьох десятиліть.
  4. C # робить існуючу кращу практику з C (падіння документа) примусовою для виконання компілятором.
  5. C # робить незвичайний випадок тим, у кого є більш явний код, а звичайний - з кодом, який записується автоматично.
  6. C # використовує той самий gotoпідхід для потрапляння на той самий блок з різних caseміток, який використовується у C. Це просто узагальнює його в деяких інших випадках.
  7. C # робить цей gotoпідхід на основі більш зручним і зрозумілішим, ніж це в C, дозволяючи caseоператорам виступати як мітки.

Загалом, досить розумне дизайнерське рішення


* Деякі форми BASIC дозволять робити те, що подобається, GOTO (x AND 7) * 50 + 240хоча хоч крихкий і, отже, особливо переконливий випадок заборони goto, слугує для показу вищого мовного еквіваленту такого способу, на якому код нижчого рівня може здійснити стрибок на основі арифметика за значенням, яке набагато розумніше, коли це результат компіляції, а не те, що потрібно підтримувати вручну. Реалізація пристрою Даффа, зокрема, добре піддається еквівалентному машинному коду або IL, оскільки кожен блок інструкцій часто буде однакової довжини, не потребуючи додавання nopнаповнювачів.

† Пристрій Даффа з'являється тут знову, як розумний виняток. Той факт, що з такою та подібними зразками відбувається повторення операцій, дозволяє зробити пропуск відносно зрозумілим навіть без явного коментаря до цього ефекту.


17

Ви можете "перейти до справи" http://www.blackwasp.co.uk/CSharpGoto.aspx

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


8

Вони відкинули таку поведінку дизайном, щоб уникнути, коли вона не використовувалася волею, а викликала проблеми.

Він може бути використаний, лише якщо в частині справи немає твердження, наприклад:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}

4

Вони змінили поведінку перемикача (з C / Java / C ++) поведінки для c #. Думаю, міркування полягали в тому, що люди забули про провал і були викликані помилки. В одній книзі, яку я прочитав, сказано, що я використовую Goto для імітації, але це не здається мені гарним рішенням.


2
C # підтримує goto, але не падіння? Ого. І це не лише ті. C # є єдиною мовою, яку я знаю, що так поводиться.
Меттью Шарлі

Спочатку мені це не сподобалось, але "падіння через" - це справді рецепт катастрофи (особливо серед молодших програмістів). кейси.) "Кенні" розмістив посилання, в якому висвітлюється елегантне використання Goto з перемикачем.
Крендель

На мою думку, це не величезна справа. У 99% випадків я не хочу провалюватися, і мене в минулому спалювали помилки.
Кен

1
"це не здається для мене гарним рішенням" - шкода, що про вас це чують, тому що це і goto caseє. Його перевага перед падінням полягає в тому, що вона є явною. Те, що деякі люди тут заперечують, goto caseпросто свідчить про те, що вони були індоктриновані проти "гото", не розуміючи цього питання і нездатні думати самостійно. Коли Дайкстра написав "GOTO вважається шкідливим", він звертався до мов, які не мали інших засобів зміни потоку управління.
Джим Балтер

@JimBalter, а потім скільки людей, які цитують Дійкстра, цитуватимуть Кнута, що "передчасна оптимізація - корінь усього зла", хоча ця цитата була тоді, коли Кнут прямо писав про те, наскільки корисним gotoможе бути оптимізація коду?
Джон Ханна

1

Ви можете домогтися пропаду як c ++ за допомогою ключового слова goto.

EX:

switch(num)
{
   case 1:
      goto case 3;
   case 2:
      goto case 3;
   case 3:
      //do something
      break;
   case 4:
      //do something else
      break;
   case default:
      break;
}

9
Якби хтось опублікував це на два роки раніше!

0

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

- документація C # switch ()


1
Я усвідомлюю, що така поведінка задокументована, я хочу знати, ЧОМУ це так, як є, та будь-які альтернативи, щоб отримати стару поведінку.
Меттью Шарлі

0

Після кожного оператора case потрібно оператор break або goto, навіть якщо це випадок за замовчуванням.


2
Якби хтось опублікував це на два роки раніше!

1
@Poldie, це було смішно в перший раз ... Шильпа вам не потрібна перерва чи перехід на кожен випадок, просто на кожен випадок із власним кодом. У вас може бути кілька справ, які добре поділяють код.
Маверик

0

Лише коротка примітка, щоб додати, що компілятор для Xamarin насправді помилився, і це дозволяє провалитись. Він нібито був виправлений, але не був звільнений. Виявив це в якомусь коді, який насправді пропадав, і компілятор не скаржився.



-1

C # не підтримує падіння за допомогою вимикачів / випадок. Не знаю чому, але насправді немає підтримки для цього. Зв'язок


Це так неправильно. Дивіться інші відповіді ... ефект неявного падіння можна отримати за допомогою явного goto case . Це був навмисний, мудрий вибір дизайнерів C #.
Джим Балтер

-11

Ви забули додати "перерва"; заява на випадок 3. У випадку 2 ви записали його в блок if. Тому спробуйте це:

case 3:            
{
    ans += string.Format("{0} hundred and ", numbers[number / 100]);
    break;
}


case 2:            
{
    int t = (number / 10) % 10;            
    if (t == 1)            
    {                
        ans += teens[number % 10];                
    }            
    else if (t > 1)                
    {
        ans += string.Format("{0}-", tens[t]);        
    }
    break;
}

case 1:            
{
    int o = number % 10;            
    ans += numbers[o];            
    break;        
}

default:            
{
    throw new ArgumentException("number");
}

2
Це дає дуже неправильний вихід. Я залишив заяви про вимикання дизайн. Питання в тому, чому компілятор C # вважає це помилкою, коли майже жодна інша мова не має цього обмеження.
Меттью Шарлі

5
Яке приголомшливе нездатність зрозуміти. А у вас було 5 років, щоб видалити це, і досі цього не зробили? Розумний.
Джим Балтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.