Вырваться из цикла while, содержащего оператор switch

У меня возникли проблемы с выяснением того, как выйти из цикла, содержащего оператор switch. Break выходит из переключателя, а не из петли.

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

Справочная информация: этот код используется в системе рабочего процесса со штрих-кодом. У нас есть карманные компьютеры со встроенными сканерами штрих-кода. Этот код используется в одной из этих функций. Он запрашивает у пользователя различные фрагменты данных на протяжении всей процедуры. Эта часть позволяет им прокручивать некоторые инвентарные записи, отображающие эту информацию на терминале PocketPC (постраничные результаты), и позволяет им вводить «D» для «Готово» и «Q» для выхода.

Вот текущий пример C#, который необходимо улучшить:

do
{
    switch (MLTWatcherTCPIP.Get().ToUpper())
    {
        case "": //scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "P": //scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "D": //DONE (exit out of this Do Loop)
            // break; // this breaks out of the switch, not the loop
            // return; // this exists entire method; not what I'm after
            keepOnLooping = false;
            break;
        case "Q": //QUIT (exit out to main menu)
            return;
        default:
            break;
    }
} while (keepOnLooping);

Вот пример кода, который делает это в VB.NET.

Do
    Select Case MLTWatcherTCPIP.Get().ToUpper
        Case "" ''#scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown()
        Case "P" ''#scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextUp()
        Case "D" ''#DONE (exit out of this Do Loop)
            Exit Do
        Case "Q" ''#QUIT (exit out to main menu)
            Return
    End Select
Loop

Спасибо,


person joshblair    schedule 31.12.2009    source источник
comment
Это выглядит нормально для меня, переменная флага — это стандартный способ проверить условие цикла.   -  person Ron Warholic    schedule 01.01.2010
comment
В Java (и некоторых других) маркировка цикла и использование разрыва с будет самым простым ответом.   -  person Roy Tinker    schedule 29.07.2016


Ответы (15)


Я нахожу эту форму немного более читабельной:

bool done = false;
while (!done) 
{ 
    switch (MLTWatcherTCPIP.Get().ToUpper()) 
    { 
        case "": //scroll/display next inventory location 
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown(); 
            break; 
        case "P": //scroll/display previous inventory location 
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown(); 
            break; 
        case "D": //DONE (exit out of this Do Loop) 
            done = true;
            break; 
        case "Q": //QUIT (exit out to main menu) 
            return; 
        default: 
            break; 
    } 
}
person Jeffrey L Whitledge    schedule 31.12.2009
comment
Почему не if/else? - person Ozkan; 24.05.2018
comment
@Ozkan Я просто отвечал на вопрос, как он был задан. На самом деле я бы никогда не использовал ToUpper() таким образом, чтобы делать сравнения без учета регистра. - person Jeffrey L Whitledge; 26.05.2018

Я бы постарался избежать этого, но вы могли бы использовать...

перейти к

Однако разъяренные толпы с вилами становятся профессиональной опасностью, если вы решите это сделать.

person Kevin Montrose    schedule 31.12.2009
comment
Более 15 лет опыта подсказывает мне, что иногда goto — лучший, быстрый и оптимизированный вариант, держать алгоритм в одном маленьком месте: держать его быстрым, эффективным, атомарным, не разрушая его на тысячу кусков, уменьшать ненужные вызовы. Примеры, анализ акустических волн, машинное зрение, реалтайм и т.д. Флаг не сделает его медленнее, я считаю это невозможным, но многие флаги могут быть проблемой чтения. - person Cheva; 30.03.2020

Один из вариантов здесь — преобразовать этот цикл в метод («метод извлечения») и использовать return.

person Marc Gravell    schedule 31.12.2009

Единственный другой способ, о котором я знаю, это ужасный goto. MSDN также говорит об этом.

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

person McAden    schedule 31.12.2009
comment
Хотя goto можно бояться, есть несколько случаев, когда он действительно полезен, и я считаю, что это один из них. - person Steve Guidi; 01.01.2010

Вы должны использовать оператор goto для многоуровневых разрывов. Кажется, это единственный «чистый» способ в С#. Использование флага также полезно, но требует дополнительного кода, если цикл имеет другие трудности для выполнения.

http://msdn.microsoft.com/en-us/library/aa664756(VS.71).aspx

Может быть интересно отметить, что некоторые другие не-c языки имеют многоуровневые разрывы, выполняя break levels; (однако Java так же бесполезна, поскольку использует goto, замаскированный под continue.. :P)

person Tor Valamo    schedule 31.12.2009

Почему бы не обернуть переключатель в метод, который возвращает логическое значение, чтобы продолжать цикл? Это имело бы побочное преимущество, сделав код более читаемым. Есть причина, по которой кто-то написал статью, в которой говорится, что нам не нужны инструкции goto ;)

do
{
    bool keepOnLooping = TryToKeepLooping();
} while (keepOnLooping);

private bool TryToKeepLooping()
{
    switch (MLTWatcherTCPIP.Get().ToUpper())
    {
        case "": //scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "P": //scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "D": //DONE (exit out of this Do Loop)
            // break; // this breaks out of the switch, not the loop
            // return; // this exists entire method; not what I'm after
            return false;
        case "Q": //QUIT (exit out to main menu)
            return true;
        default:
            break;
    }

    return true;
}
person Jeffrey Cameron    schedule 31.12.2009

Вы не можете легко вырваться из внешнего цикла, но вы можете его continue.

Если вы измените свою логику, то вы получите это. Обратите внимание, что сразу после оператора switch есть break для выхода из цикла.

На мой взгляд, это не очень читаемый код, и я думаю, что флаг все же лучше.

   do
         {
            switch (Console.ReadKey().KeyChar.ToString())
            {
                case "U":
                    Console.WriteLine("Scrolling up");
                    continue;

                case "J":
                    Console.WriteLine("Scrolling down");
                    continue;

                case "D": //DONE (exit out of this Do Loop)
                    break;

                case "Q": //QUIT (exit out to main menu)
                    return;

                default:
                    Console.WriteLine("Continuing");
                    continue;
            }

            break;

        } while (true);

        Console.WriteLine("Exited");
person Simon_Weaver    schedule 01.08.2012

Флаг — стандартный способ сделать это. Единственный другой способ, который я знаю, это использовать goto.

person John Knoeller    schedule 31.12.2009

Вы можете заменить оператор switch оператором if/else. goto не требуется, и оператор break выходит из цикла:

do
{
  String c = MLTWatcherTCPIP.Get().ToUpper();

  if (c = "")
    MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
  else if (c = "P")
    MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextUp();
  else if (c = "D")
     break;
  else if (c = "Q")
    return;
  else
  {
    // Handle bad input here.
  }
} while (keepLooping)
person Chris R. Timmons    schedule 31.12.2009
comment
Хотя это подходит для большинства приложений, существует важное различие между switch и if/else, заключающееся в том, что компилятор обычно может оптимизировать операции ветвления с помощью таблицы переходов, что приводит к более быстрому коду. - person Steve Guidi; 01.01.2010
comment
Стив, код здесь ожидает ввода пользователя. Он уже просидел там миллиарды наносекунд, абсолютно ничего не делая. Кажется совершенно неважным, требуется ли одна или сто наносекунд для определения того, что напечатал символ. Оптимизация должна основываться на эмпирических данных о реальных проблемах пользователей, а не на предположениях о том, что может сделать компилятор. - person Eric Lippert; 01.01.2010
comment
Я согласен с аргументом Стива, но в некоторых простых ситуациях изменение вашей логики на if/else поможет. - person Alex; 22.03.2012

Оберните его в функцию и используйте оператор return для выхода. Как насчет этого?

person Hamish Grubijan    schedule 31.12.2009

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

if(!keepOnLooping)
  break;

Но на самом деле это не отличается с точки зрения исполнения.

person Fry    schedule 31.12.2009

Напишите что-то вроде:

case "Exit/Break" :
                  //Task to do
                    if(true)
                      break;

Этот перерыв не будет связан ни с одним делом. Он будет принадлежать циклу while.

person prashant chaudhary    schedule 10.10.2013

Вы можете изменить оператор switch на цикл for/foreach. Как только условие выполнено, установите «keepOnLooping» в false, а затем используйте break, чтобы выйти из цикла. Остальное должно позаботиться о себе.

person Chuck Conway    schedule 31.12.2009
comment
переключиться на for/foreach? Вы имели в виду что-то другое. - person nawfal; 15.10.2013

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

do
{
    var expression = MLTWatcherTCPIP.Get().ToUpper();
    if (expression = "D") //DONE (exit out of this Do Loop)
    {   
        statement;
        break;
    }

    switch (expression)
    {
        case "": //scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "P": //scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "Q": //QUIT (exit out to main menu)
            return;
        default:
            break;
    }
} while (true); //or whatever your condition is

Вы также можете сделать само case частью условия цикла while, учитывая, что вам нужно только выйти из цикла, а само вычисление выражения тривиально (например, чтение переменной).

do
{
    switch (expression)
    {
        case "": //scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "P": //scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "Q": //QUIT (exit out to main menu)
            return;
        default:
            break;
    }
} while (condition && expression != "D");

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

person nawfal    schedule 15.10.2013

Может сработать, а может и не сработать, но лямда, почему бы не попробовать просто ради удовольствия

while(  (expr) => (){
switch(expr){
case 1: dosomething; return true; 
case 2 : something;return true;
case exitloop:return false;}
});   
person lamdamaybe    schedule 23.08.2017