Почему бы не форсировать явный провал, а не явный разрыв для операторов switch?

Это не конкретно вопрос C#, но именно версия switch для C# заставляет меня задать вопрос.

Почему я должен явно заявлять, что я не хочу переходить от одного оператора case к другому, вместо того, чтобы указывать, когда я ДЕЙСТВИТЕЛЬНО хочу провалиться?

В C# компилятор не позволит вам переходить от одного оператора case к следующему (если только оператор case не пуст), и все же он вынуждает вас ставить явный break в конце каждого. Подавляющее большинство утверждений switch, которые я пишу, не проваливаются.

int x = 4;
string result;

switch (x)
{
case 1:
   result = "One";
   break;
case 2:
   result = "A couple";
   break;
case 3:
case 4:
   result = "Three or four";
   break;
/*
case 5:
   result = "Five";   // Here be an error! A common typo for me!!!
*/
default:
   result = "Five or more";
   break;
}

Приведенный выше пример призван показать возможности. Количество раз, когда я действительно проваливаюсь (например, между 3 и 4 выше), минимально. Если я удалю комментарии из случая 5 выше, компилятор C# поймает это как ошибку. Почему я должен исправлять это вручную, если компилятор прекрасно знает, что я пытаюсь сделать?

Не лучше ли будет следующее?

int x = 4;
string result;

switch (x)
{
case 1:
   result = "One";
case 2:
   result = "A couple";
case 3:
   continue;   // or even goto 4
case 4:
   result = "Three or four";
/*
case 5:
   result = "Five";   // This would not longer be an error!! Hooray!
*/
default:
   result = "Five or more";
}

Позвольте мне прояснить, что я не выступаю за провал здесь. В конце каждого оператора case подразумевается break. Два примера должны быть функционально эквивалентны.

Компилятор все еще может накричать на меня, если я забуду оператор continue в случае 3. Однако это случалось нечасто, и я был бы рад помощи. На самом деле, вероятность того, что я просто пропустил заполнение скелетного кода, в этом случае довольно высока. Полезный. Говорить мне, что я забыл оператор break, никогда не бывает полезно. Это просто более занятая работа, чтобы сделать компилятор счастливым.

Похоже, что это, вероятно, просто удержание от C (что позволяет вам провалиться, даже если у вас есть код в операторе case). Есть ли более глубокая причина, которую я не вижу?

Только сегодня утром я просматривал код компилятора и обнаружил сотни case операторов в switch, которые ничего не делали, кроме как возвращали коды операций. У каждого из них было заявление break. Столько писанины и хлама...

Что думают люди? Почему второй вариант не лучше по умолчанию?


person Justin    schedule 09.08.2011    source источник
comment
Я согласен. Похоже, что синтаксис должен упростить использование по умолчанию и создать ключевое слово для крайнего случая. Может быть, это была попытка сохранить по-кишшски? Просто предположение.   -  person Ed S.    schedule 09.08.2011
comment
Потому что C# был разработан опытными программистами, которые очень хорошо знали, что не так в C-версии оператора switch. Забыть написать break; — невероятно распространенная ошибка. Используйте ключевое слово, барабанная дробь, goto.   -  person Hans Passant    schedule 09.08.2011
comment
@ Ганс - О, я согласен. С# правильно понял, что не допускает провалов. Но, поскольку он не допускает провала, почему я должен говорить ему, чтобы он НЕ пропускал? Это не собирается делать это в любом случае. Мои инструкции на самом деле не добавляют никакой новой информации, которую компилятор еще не применяет к себе. Я показываю использование «goto» выше именно так, как я думаю, что вы имеете в виду.   -  person Justin    schedule 09.08.2011
comment
Ну, это тоже должно было быть узнаваемо еще 10 лет назад. Если бы это не было ограничением, они бы приняли версию vb.   -  person Hans Passant    schedule 09.08.2011


Ответы (3)


Обоснование операторов C# switch заключается в том, что поведение C заключается в том, что случаи проваливаются, если вы явно не выйдете с оператором, таким как break, return или goto. Это означает, что все, кто знаком с C (а это, вероятно, большинство программистов на C#), будут ожидать, что синтаксис C# switch будет означать примерно то же самое. Если бы разработчики C# сделали так, чтобы случаи не не выпадали, код работал бы в точности противоположно тому, что ожидают пользователи!

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

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

person Gabe    schedule 09.08.2011
comment
Спасибо Гейб. Принцип наименьшего удивления — довольно правильный ответ на мой вопрос. Одна вещь, однако, C # уже не проваливается (если оператор case не пуст). Вы не можете забыть добавить «перерыв» и получить ошибку в C#. Не включение «перерыва» является ошибкой времени компиляции. Таким образом, «переключатель» C# на самом деле ведет себя совсем не так, как ожидают программисты на C. Тем не менее, как только код действительно написан для успешной сборки, полученный код действительно имитирует то, что программисты C (и C++) ожидают от аналогичного кода C, как вы говорите. - person Justin; 09.08.2011
comment
@Justin: Хороший вопрос. Что касается switch, то код ведет себя так, как ожидает программист на C, в то время как компилятор ведет себя немного иначе (требует прерывания для непустого случая). - person Gabe; 09.08.2011

Операторы switch действительно сияют в том случае, когда происходят подобные вещи (для замены ifs)

switch (my_var) {
    case 1:
    case 2:
    case 3:
    case 4:
        do_something();
        break;
    case 5:
    case 6:
    case 7:
    case 8:
        do_something else();
        break;
}

Это сильно заменяет либо вложенные операторы if, либо гигантские операторы if с операторами and в них.

Другая основная причина заключается в том, что он как бы заимствован из C. Операторы case выглядят почти точно так же, как сборка, которая будет сгенерирована для них компилятором (в некоторых случаях). Не совсем уверен, почему они сначала выбрали этот дизайн, но я действительно не вижу проблемы, так как такое же количество «если» может также показаться загроможденным (хотя и не всегда).

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

person Jesus Ramos    schedule 09.08.2011
comment
Спасибо. Я предполагаю, что мой собственный опыт показывает, что операторы switch, подобные приведенным выше, не являются нормой. Кроме того, если вы посмотрите на свой пример, вы можете сделать то же самое с одним оператором if/else. Поскольку операторы switch — это просто сахар для деревьев if/else, почему бы не адаптировать их к деревьям с наибольшим количеством ветвей? - person Justin; 09.08.2011
comment
Иногда это намного легче читать, также для операторов if, которые начинают занимать несколько строк, это может привести к путанице. Кроме того, мой пример не совсем то, что я хотел, а просто что-то, чтобы донести суть. (Я построил дизассемблер кода операции, и в этом случае нам намного проще переключаться, чем многострочные if'ы) - person Jesus Ramos; 09.08.2011

Причина того, что C и C++ имеют неявный отказ, заключается в обратной совместимости с огромным количеством кода, который зависит от него, хотя и редко.

Причина, по которой C# не имеет неявного провала, заключается в том, что в большинстве случаев это ошибка. Причина, по которой C# не имеет неявного разрыва, заключается в том, что это было бы неожиданностью для программистов на C, C++ и Java. И это даже не будет генерировать предупреждение? Тихие неожиданные неудачи — это очень плохо.

Требовать, чтобы поведение было явным, — самый безопасный вариант, и C# выбрал именно этот путь. (и я бы хотел, чтобы C++ разработал явный синтаксис для провала, чтобы компиляторы могли предупреждать о неявном провале)

person Ben Voigt    schedule 09.08.2011
comment
На самом деле это довольно распространено! Устройства Duff не будут работать без провалов, многие конечные автоматы используют провалы (это особенно удобно, когда условие, сигнализирующее о конце одного состояния, удваивается как начало нового состояния). - person James Anderson; 09.08.2011
comment
@James: И это все еще очень небольшая часть всех операторов switch. Я считаю, что то, что я сказал, совершенно правильно: огромное количество кода нуждается в этом, но это небольшая часть всего использования (редко). - person Ben Voigt; 09.08.2011