Выключить инструкцию в С#?
Падение прокси-коммутатора является одной из моих личных основных причин для любви к конструкциям 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");
}