Переключить оператор падения в C #?

Падение заявления переключателя - одна из моих личных главных причин любить 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с?

6.10.2008 13:00:15
14 ОТВЕТОВ
РЕШЕНИЕ

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

Падение switch- cases может быть достигнуто без кода в 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;
}
644
23.05.2017 10:31:36
Я думаю, что в данном конкретном случае goto не считается вредным.
Thomas Owens 6.10.2008 19:32:08
Черт, я программировал на C # с первых дней существования 1.0, и до сих пор никогда не видел этого. Просто идет, чтобы показать, вы узнаете новые вещи каждый день.
Erik Forbes 5.02.2009 03:30:12
Это все хорошо, Эрик. Единственная причина, по которой я знал об этом, в том, что я ботаник-теоретик, который читает спецификации ECMA-334 с помощью лупы.
Alex Lyman 6.02.2009 10:19:01
@Dancrumb: На момент написания этой функции C # еще не добавлял «мягких» ключевых слов (таких как «yield», «var», «from» и «select»), поэтому у них было три реальных варианта: 1 ) сделать «падение» жестким ключевым словом (вы не можете использовать его в качестве имени переменной), 2) написать код, необходимый для поддержки такого мягкого ключевого слова, 3) использовать уже зарезервированные ключевые слова. # 1 был большой проблемой для тех, кто переносил код; № 2 был довольно большой инженерной задачей, насколько я понимаю; и вариант, с которым они пошли, # 3 имел дополнительное преимущество: другие разработчики, читающие код после факта, могли узнать об этой функции из базовой концепции goto
Alex Lyman 14.10.2011 19:43:26
Все это говорит о новых / специальных ключевых словах для явного падения. Разве они не могли просто использовать ключевое слово continue? Другими словами. Вырваться из выключателя или перейти к следующему случаю (провалиться).
Tal Even-Tov 12.11.2013 11:32:26

«Почему» состоит в том, чтобы избежать случайного провала, за что я благодарен. Это не редкий источник ошибок в 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 - есть некоторые полезные изменения, которые могут быть сделаны с точки зрения области видимости и т. Д. Возможно, был бы полезен более разумный переключатель, который мог бы выполнять сопоставление с образцом и т. Д., Но это действительно переключение с переключателя на «проверку последовательности условий» - в этот момент может потребоваться другое имя.

44
11.04.2018 21:24:52
В этом и заключается разница между switch и if / elseif. Переключатель предназначен для проверки различных состояний одной переменной, тогда как if / elseif может использоваться для проверки любого количества подключенных объектов, но не обязательно одной или той же переменной.
Matthew Scharley 6.10.2008 13:19:58
Если бы это было для предотвращения случайного провала, я чувствую, что предупреждение компилятора было бы лучше. Точно так же, как у вас, если в вашем заявлении if есть задание:if (result = true) { }
Tal Even-Tov 12.11.2013 11:38:16
@ TalEven-Tov: предупреждения компилятора действительно должны быть в тех случаях, когда вы почти всегда можете исправить код, чтобы он был лучше. Лично я предпочел бы неявное нарушение, так что это не было бы проблемой для начала, но это другой вопрос.
Jon Skeet 12.11.2013 11:52:19

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

switch(value)
{
    case 1:// this is still legal
    case 2:
}
26
6.10.2008 13:06:10
Я хорошо никогда не понимаю, почему это не «случай 1, 2:»
BCS 9.02.2009 23:02:02
@ Дэвид Пфеффер: Да, и так case 1, 2:на языках, которые позволяют это. Чего я никогда не пойму, так это того, почему любой современный язык не захотел бы этого допустить.
BCS 18.10.2010 13:54:46
@BCS с оператором goto, несколько вариантов, разделенных запятыми, могут быть сложными для обработки?
penguat 13.09.2011 12:44:48
@pengut: было бы точнее сказать, что case 1, 2:это один ярлык, но с несколькими именами. - FWIW, я думаю, что большинство языков, которые запрещают проваливать, не являются специальным регистром с указателем «последовательные метки регистра», а рассматривают метки регистра как аннотацию к следующему оператору и требуют последнего оператора перед оператором, помеченным (один или больше) кейсы для прыжков.
BCS 13.09.2011 16:23:51

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

И когда вы думаете об этом, код так:

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

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

Таким образом, тот факт, что on должен быть явным с переходом в C #, не добавляет никаких штрафов тем, кто хорошо писал на других языках стиля 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 явно не кодом 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наполнителей.

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

22
31.10.2014 14:11:19

Вы можете «перейти к метке кейса» http://www.blackwasp.co.uk/CSharpGoto.aspx

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

16
1.10.2016 20:29:50

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

Его можно использовать только в том случае, если в части кейса нет оператора, например:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}
8
6.10.2008 13:04:34

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

4
6.10.2008 13:03:03
C # поддерживает Goto, но не Fallthrough? Вау. И это не только те. C # единственный известный мне язык, который ведет себя таким образом.
Matthew Scharley 6.10.2008 13:06:22
Поначалу мне это не совсем нравилось, но «падение» действительно является рецептом катастрофы (особенно среди младших программистов). Как многие отмечали, C # по-прежнему допускает падение для пустых строк (что составляет большинство случаи.) "Кенни" опубликовал ссылку, которая подчеркивает элегантное использование Goto с switch-case.
Pretzel 6.10.2008 13:39:35
На мой взгляд, это не так уж важно. В 99% случаев я не хочу проваливаться, и в прошлом меня жгли жуки.
Ken 6.10.2008 14:08:36
«Это не похоже на хорошее решение для меня» - жаль слышать это о вас, потому что это то goto case, для чего. Его преимущество перед прорывом в том, что оно явное. То, что некоторые люди здесь возражают против goto caseтого, чтобы просто показать, что им внушали «goto» без какого-либо понимания вопроса и не способны думать самостоятельно. Когда Дейкстра написал «GOTO рассмотрен вредно», он обращался к языкам, у которых не было никаких других средств изменения потока управления.
Jim Balter 24.10.2013 09:03:30
@JimBalter, а затем, сколько людей, которые цитируют Дейкстру, цитируют Кнута, что «преждевременная оптимизация - корень всего зла», хотя эта цитата была, когда Кнут явно писал о том, насколько полезным gotoможет быть оптимизация кода?
Jon Hanna 8.12.2013 15:00:21

Вы можете добиться провала как 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;
}
1
28.07.2010 20:30:50
Если бы только кто-то опубликовал это два года назад!
user146043 13.03.2012 15:25:32

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

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

0
6.10.2008 13:03:34
Я понимаю, что это поведение задокументировано, я хочу знать, ПОЧЕМУ так оно и есть, и любые альтернативы, чтобы получить старое поведение.
Matthew Scharley 6.10.2008 13:05:13

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

0
4.06.2010 09:02:02
Если бы только кто-то опубликовал это два года назад!
user146043 13.03.2012 15:25:39
@ Полди, это было забавно в первый раз ... Шилпа, тебе не нужно делать перерыв или переход для каждого случая, только для каждого случая со своим собственным кодом. У вас может быть несколько случаев, когда код делится просто отлично.
Maverick 14.10.2013 22:05:38

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

0
14.02.2014 22:19:05

переключатель (C # Reference) говорит

C # требует конца разделов переключателя, включая последний,

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

0
7.10.2014 22:53:24
Спасибо, это помогло мне;)
CareTaker22 15.03.2016 18:12:34

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

-1
6.10.2008 13:04:09
Это так неправильно. Посмотрите другие ответы ... Эффект неявного падения может быть получен с помощью явного goto case . Это был намеренный, мудрый выбор дизайна дизайнерами C #.
Jim Balter 24.10.2013 09:09:14

Вы забыли добавить «перерыв»; утверждение в случае 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");
}
-11
6.10.2008 13:10:37
Это приводит к неправильному выводу. Я оставил выключатели в дизайне. Вопрос в том, почему компилятор C # видит это как ошибку, когда почти ни в одном другом языке нет такого ограничения.
Matthew Scharley 6.10.2008 13:13:03
Какая потрясающая неспособность понять. И у вас было 5 лет, чтобы удалить это и до сих пор не сделали этого? Mindboggling.
Jim Balter 24.10.2013 09:07:01