Выключить инструкцию в С#?

Падение прокси-коммутатора является одной из моих личных основных причин для любви к конструкциям switch vs. 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;
}

Умные люди cringing, потому что string[] должен быть объявлен вне функции: ну, они есть, это просто пример.

Сбой компилятора со следующей ошибкой:

Control cannot fall through from one case label ('case 3:') to another
Control cannot fall through from one case label ('case 2:') to another

Почему? И есть ли способ получить такое поведение без трех if s?

Ответ 1

(Скопируйте/вставьте ответ, который я предоставил в другом месте)

Падение через 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;
}

Ответ 2

"Почему", чтобы избежать случайного провала, за что я благодарен. Это не редкий источник ошибок в C и pre-1.5 Java.

Обходной путь заключается в использовании goto, например.

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

Общий дизайн коммутатора/корпуса немного неудачен, на мой взгляд. Это слишком близко к C - есть некоторые полезные изменения, которые могут быть сделаны с точки зрения охвата и т.д. Возможно, более разумный переключатель, который мог бы выполнять сопоставление образцов и т.д., Был бы полезен, но это действительно изменилось с перехода на "проверку последовательности условий", - в этот момент, возможно, потребуется другое имя.

Ответ 3

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

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

Ответ 4

Чтобы добавить к ответам здесь, я думаю, что стоит рассмотреть противоположный вопрос в связи с этим, а именно: почему C разрешил провал в первую очередь?

Любой язык программирования, конечно, служит двум целям:

  • Предоставить инструкции для компьютера.
  • Оставьте запись о намерениях программиста.

Таким образом, создание любого языка программирования является балансом между тем, как наилучшим образом служить этим двум целям. С одной стороны, проще всего превратиться в компьютерные инструкции (будь то машинный код, байт-код, например, IL, или инструкции интерпретируются при исполнении), тогда более возможно, что процесс компиляции или интерпретации будет эффективным, надежным и компактный выход. Достигнутая до предела, эта цель приводит к простому написанию в сборке, IL или даже необработанных op-кодах, потому что самая простая компиляция - это то, где компиляции вообще нет.

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

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

В этом случае есть две веские причины для разрешения провала:

  • В любом случае это происходит естественным образом: если вы построите таблицу перехода в набор инструкций, а одна из ранних партий команд не содержит какого-либо скачка или возврата, то выполнение просто естественным образом перейдет в следующей партии. Разрешить провал - это то, что "просто произойдет", если вы включили switch -using C в машинный код с помощью таблицы перехода.

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

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

Спустя четыре десятилетия все не так, по нескольким причинам:

  • Coders in C сегодня могут иметь мало или вообще не иметь опыта сборки. Кодеры во многих других языках C-стиля еще менее вероятны (особенно Javascript!). Любая концепция "того, что люди привыкли к сбору" больше не имеет отношения.
  • Улучшения в оптимизации означают, что вероятность того, что switch превратится в if-else, потому что считалось, что подход, который может быть наиболее эффективным, или же превратился в особенно эзотерический вариант подхода в прыжковой таблице, выше. Отображение между подходами более высокого и нижнего уровня не так сильно, как когда-то было.
  • Опыт показал, что спад имеет тенденцию быть случаем меньшинства, а не нормой (исследование компилятора Sun обнаружило, что 3% блоков switch использовали провал, отличный от нескольких меток на одном блоке, и это считалось, что прецедент здесь означает, что эти 3% были на самом деле намного выше, чем обычно). Таким образом, язык, изученный, делает необычное более легко удовлетворяемое, чем обычное.
  • Опыт показал, что спад, как правило, является источником проблем как в случаях, когда он случайно выполнен, так и в тех случаях, когда кто-то, кто поддерживает код, пропускает правильный провал. Это последнее является тонким дополнением к ошибкам, связанным с провалом, потому что даже если ваш код абсолютно без ошибок, ваш провал может все еще вызвать проблемы.

В связи с этими двумя последними пунктами рассмотрим следующую цитату из текущей версии K & R:

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

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

Таким образом, из устья лошади падение в C проблематично. Он считал, что хорошая практика всегда документирует провалы с комментариями, что является приложением общего принципа, согласно которому нужно документировать, где кто-то делает что-то необычное, потому что то, что будет позже проверять код и/или сделать ваш код похожим на него имеет в нем новичковую ошибку, когда она на самом деле правильная.

И когда вы думаете об этом, код выглядит следующим образом:

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

Является добавлением чего-то, чтобы сделать провальное явное в коде, это просто не то, что может быть обнаружено (или чье отсутствие может быть обнаружено) компилятором.

Таким образом, тот факт, что on должен быть явным с провалом в С#, не добавляет никакого штрафа тем людям, которые хорошо пишут в других языках 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. (Конечно, есть способы и способы избежать этого с помощью разных условностей, но это касается всего, что связано с этим вопросом). Таким образом, С#, основанный на уже нормальном способе решения одной ситуации, когда мы хотим ударить более одного блока кода в switch, и просто обобщил его, чтобы охватить также провал. Это также сделало оба случая более удобными и самодокументирующими, так как мы должны добавить новую метку в C, но можем использовать case как метку на С#. В С# мы можем избавиться от метки below_six и использовать goto case 5, который более ясен в отношении того, что мы делаем. (Мы также должны добавить break для default, который я оставил, чтобы сделать код C выше явно не С# -кодом).

Таким образом:

  • С# больше не относится к неоптимизированному выходу компилятора так же, как C-код, сделанный 40 лет назад (и не C в наши дни), что делает одно из вдохновений неудачного несоответствия.
  • С# остается совместимым с C не только с неявным break, для более простого изучения языка теми, кто знаком с подобными языками, и упрощения переноса.
  • С# удаляет возможный источник ошибок или неправильно понимаемый код, который был хорошо документирован как вызывающий проблемы в течение последних четырех десятилетий.
  • С# делает существующую передовую практику с C (провалом документа), подлежащим исполнению компилятором.
  • С# делает необычный случай с более явным кодом, в обычном случае тот, у кого код просто записывается автоматически.
  • С# использует тот же подход на основе goto для удара одного и того же блока из разных меток case, как это используется в C. Он просто обобщает его на некоторые другие случаи.
  • С# делает этот подход goto более удобным и понятным, чем он есть в C, позволяя операторам case действовать как метки.

В целом, довольно разумное дизайнерское решение


* Некоторые формы BASIC позволили бы сделать подобным GOTO (x AND 7) * 50 + 240, который в то время как хрупкий и, следовательно, особенно убедительный случай для запрещения goto, служит для отображения эквивалента на более высоком языке такого типа, который ниже -level code может сделать переход на основе арифметики на значение, что гораздо разумнее, если результат компиляции, а не что-то, что нужно поддерживать вручную. Реализации Duff Device, в частности, хорошо подходят для эквивалентного машинного кода или IL, потому что каждый блок инструкций часто имеет одинаковую длину без необходимости добавления наполнителей nop.

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

Ответ 5

Вы можете 'goto case label' http://www.blackwasp.co.uk/CSharpGoto.aspx

Оператор goto - простая команда, которая безоговорочно передает управление программой другому оператору. Некоторые разработчики часто критикуют разработчиков за то, что они отказываются от всех языков программирования высокого уровня, потому что это может привести к коду спагетти. Это происходит, когда есть так много операторов goto или подобных операторов перехода, что код становится трудно читать и поддерживать. Тем не менее, есть программисты, которые отмечают, что инструкция goto при тщательном использовании предоставляет элегантное решение некоторых проблем...

Ответ 6

Они не учитывали это поведение по дизайну, чтобы избежать, когда он не был использован волей, но вызвал проблемы.

Он может использоваться только в том случае, если в части case нет инструкции, например:

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

Ответ 7

Они изменили инструкцию switch (из C/Java/С++) для С#. Я предполагаю, что рассуждение состояло в том, что люди забыли о провале, и были вызваны ошибки. Одна книга, которую я прочитал, сказал, чтобы использовать goto для симуляции, но это не похоже на хорошее решение для меня.

Ответ 8

После каждого оператора case требуется break или goto, даже если это случай по умолчанию.

Ответ 9

Оператор перехода, такой как разрыв, требуется после каждого блока, включая последний блок, является ли он выражение о делах или дефолт выражение. За одним исключением (в отличие от оператор С++), С# не поддерживать неявное падение один ярлык к другому. Тот самый исключение - если в случае нет кода.

- Документация С# switch()

Ответ 10

С# не поддерживает падение с помощью операторов switch/case. Не знаю, почему, но на самом деле нет никакой поддержки. Связь

Ответ 11

Вы можете достичь падения, как С++, с помощью ключевого слова goto.

Пример:

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

Ответ 12

Просто добавьте, что компилятор для Xamarin действительно получил это неправильно, и он позволяет провалиться. Он якобы был исправлен, но не был выпущен. Обнаружил это в некотором коде, который действительно проваливался, и компилятор не жаловался.

Ответ 13

switch (ссылка С#) говорит

С# требует окончания секций коммутатора, включая конечный,

Итак, вам также нужно добавить break; в ваш раздел default, иначе будет ошибка компилятора.

Ответ 14

Вы забыли добавить оператор "break;" в случай 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");
}