Почему оператор continue не может находиться внутри блока finally?


107

У меня нет проблем; Мне просто любопытно. Представьте себе следующий сценарий:

foreach (var foo in list)
{
    try
    {
         //Some code
    }
    catch (Exception)
    {
        //Some more code
    }
    finally
    {
        continue;
    }
}

Это не скомпилируется, поскольку вызывает ошибку компилятора CS0157 :

Контроль не может покинуть тело предложения finally

Зачем?


7
Так. Просто любопытно. Если вам совершенно понятно, почему он не компилируется, почему вы хотите, чтобы кто-то объяснил то, что уже имеет смысл? =)
— J. Steen

14
зачем вам continue;в finallyблоке? разве это не то же самое, что continue;после try -- catchблока?
— bansi

6
@ J.Steen Нет, я ДЕЙСТВИТЕЛЬНО знаю, что finally/continueэто ограничение компилятора C # :-) Мне тоже интересно узнать причину этого ограничения.
— xanatos 01

5
@xanatos - Технически говоря, это ограничение базового CIL. Из спецификации языка : «Передача управления никогда не разрешается вводить обработчик catch или предложение finally, кроме как через механизм обработки исключений». и «Передача управления из защищенной области разрешена только с помощью инструкции исключения (leave, end.filter, end.catch или end.finally)». brСемейство команд ветвления не может достичь этого.
— Без подписи

1
@Unsigned: это тоже хорошее ограничение, разрешить это было бы странно :)
— user7116

Ответы:


150

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

Даже если исключение не возникло, он finallyбудет запущен, когда будут выполняться другие операторы передачи управления внутри блока try / catch, например return, например, который вызывает ту же проблему.

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

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

C #, вы и выход в случае успеха

Если вы хотите игнорировать исключения (чаще всего это плохая идея) и продолжать выполнение цикла, используйте блок catch all:

foreach ( var in list )
{
    try{
        //some code
    }catch{
        continue;
    }
}

Если вы хотите делать это continueтолько тогда, когда не возникают неперехваченные исключения, просто поместите его continueза пределы блока try.


12
Я приму это как ответ, потому что вы понимаете причины, по которым Microsoft, возможно, решила не принимать продолжение в файле finally. Наверное, эти изображения меня тоже убедили :)
— lpaloub 01

20
Можете ли вы развить идею «бросить в яму успеха»? Я не понял :-D
— Ant


13
Изображение было сделано Джоном Скитом, кстати. Я получил отсюда: msmvps.com/blogs/jon_skeet/archive/2010/09/02/…
— Р. Мартиньо Фернандес

1
Конечно, a continueв finallyблоке будет в порядке, если он будет продолжать только локальный цикл, определенный внутри finallyблока. Однако в вопросе он пытается продолжить «внешний» цикл. Подобные заявления , что вы не можете иметь внутри finallyблока находятся return, break(при дроблении из блока) и goto(при переходе к метке за пределами finallyблока). Связанное обсуждение Java см. В разделе Возврат из блока finally в Java .
— Йеппе Стиг Нильсен

32

Вот надежный источник:

Оператор continue не может выйти из блока finally (раздел 8.10). Когда оператор continue встречается в блоке finally, цель оператора continue должна находиться в том же блоке finally; в противном случае возникает ошибка времени компиляции.

Это взято из MSDN, 8.9.2 Оператор continue .

В документации сказано, что:

Операторы блока finally всегда выполняются, когда элемент управления выходит из оператора try. Это верно независимо от того, происходит ли передача управления в результате нормального выполнения, в результате выполнения оператора break, continue, goto или return или в результате распространения исключения из оператора try. Если во время выполнения блока finally возникает исключение, исключение распространяется на следующий включающий оператор try. Если другое исключение находилось в процессе распространения, это исключение теряется. Процесс распространения исключения обсуждается далее в описании оператора throw (раздел 8.9.5).

Отсюда. 8.10 Оператор try .


31

Вы можете подумать, что это имеет смысл, но на самом деле это не имеет смысла .

foreach (var v in List)
{
    try
    {
        //Some code
    }
    catch (Exception)
    {
        //Some more code
        break; or return;
    }
    finally
    {
        continue;
    }
}

Что вы намереваетесь сделать паузу или продолжить при возникновении исключения? Команда компилятора C # не хочет принимать решение самостоятельно, предполагая breakили continue. Вместо этого они решили пожаловаться на то, что ситуация с разработчиком будет неоднозначной с передачей управления finally block.

Так что задача разработчика - четко указать, что он намерен делать, а не компилятор, предполагающий что-то еще.

Надеюсь, вы понимаете, почему это не компилируется!


В связи с этим, даже если бы не было «уловки», путь выполнения после finallyоператора будет зависеть от того, catchзавершился ли оператор через исключение, и нет никакого механизма, чтобы сказать, как это должно взаимодействовать с a continue. Однако мне нравится ваш пример, потому что он показывает еще большую проблему.
— supercat 02

@supercat Согласитесь с вами, мой ответ показывает пример неоднозначной ситуации для компилятора, но с этим подходом так много проблем.
— Шрирам Сакхивел 02

16

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

Вы, вероятно, думаете о таком сценарии:

public static object SafeMethod()
{
    foreach(var item in list)
    {
        try
        {
            try
            {
                //do something that won't transfer control outside
            }
            catch
            {
                //catch everything to not throw exceptions
            }
        }
        finally
        {
            if (someCondition)
                //no exception will be thrown, 
                //so theoretically this could work
                continue;
        }
    }

    return someValue;
}

Теоретически вы можете отследить поток управления и сказать «да, это нормально». Исключение не создается, управление не передается. Но у разработчиков языка C # были другие проблемы.

Выброшенное исключение

public static void Exception()
{
    try
    {
        foreach(var item in list)
        {
            try
            {
                throw new Exception("What now?");
            }
            finally
            {
                continue;
            }
        }
    }
    catch
    {
        //do I get hit?
    }
}

Ужасный Гото

public static void Goto()
{
    foreach(var item in list)
    {
        try
        {
            goto pigsfly;
        }
        finally
        {
            continue;
        }
    }

    pigsfly:
}

Возврат

public static object ReturnSomething()
{
    foreach(var item in list)
    {
        try
        {
            return item;
        }
        finally
        {
            continue;
        }
    }
}

Расставание

public static void Break()
{
    foreach(var item in list)
    {
        try
        {
            break;
        }
        finally
        {
            continue;
        }
    }
}

Итак , в заключении, да, хотя это небольшая возможность использования continueв ситуациях , когда управление не передаются, но много (большинство?) Случаев включают исключение или returnблоки. Разработчики языка посчитали, что это будет слишком двусмысленно и (вероятно) невозможно гарантировать во время компиляции, что your continueиспользуется только в тех случаях, когда поток управления не передается.


Получил ту же ситуацию в java, когда proguard вылетал во время обфускации. Ваше объяснение довольно хорошее. C # выдает ошибку времени компиляции - это имеет смысл.
— Dev_Vikram 09

11

Вообще continueне имеет смысла при finallyблочном использовании. Взгляните на это:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}

«В общем» многое не имеет смысла. И это не значит, что он не должен работать «в частности». IE: continueне имеет смысла вне оператора цикла, но это не значит, что он не поддерживается.
— zerkms 01

Я думаю, в этом есть смысл. itemможет быть файлом, чтение не удалось -> finallyфайл закрывается. continueпредотвращает остальную обработку.
— jnovacho 01

5

"Это не компилируется, и я думаю, что это имеет смысл"

Думаю, нет.

Когда у вас буквально есть, catch(Exception)то вам не нужно последнее (и, вероятно, даже не continue).

Когда у вас есть более реалистичный catch(SomeException), что должно произойти, если исключение не обнаружено? Вы continueхотите идти одним путем, а исключение обрабатывает другое.


2
Я думаю, что тебе может понадобиться, finallyкогда будет catch. Обычно он используется для надежного закрытия ресурсов.
— jnovacho 01

Но это не просто исключения. Вы легко можете разместить returnоператор в своих блоках tryили catch. Что же нам теперь делать? Вернуться или продолжить цикл? (и если мы продолжим, что, если это последний элемент для итерации? Тогда мы просто продолжим за пределами цикла, foreachи возврат никогда не произойдет?) РЕДАКТИРОВАТЬ: Или даже gotoна метку вне цикла, практически любое действие, которое передает управление за пределы foreachцикла .
— Крис Синклер

2
Я не согласен с тем, что «когда у вас буквально есть catch (Exception), вам не нужно final (и, вероятно, даже продолжение).». Что делать, если мне нужно выполнить операцию независимо от того, возникло ли исключение или нет?
— lpaloub 01

Хорошо, наконец-то обслуживает дополнительные returns внутри блока. Но обычно исполнение продолжается после того, как все уловили.
— Хенк Холтерман

«наконец» - это не «улов». Его следует использовать для кода очистки. блок finally выполняется независимо от того, выброшено исключение или нет . Такие вещи, как закрытие файлов или освобождение памяти (если вы используете неуправляемый код) и т. Д., Это то, что вы вставляете в блок finally
— Роботник

3

Вы не можете покинуть тело блока finally. Это включает ключевые слова break, return и, в вашем случае, continue.


3

finallyБлок может быть выполнен с исключением ждать , чтобы быть вызваны повторно. На самом деле было бы бессмысленно иметь возможность выйти из блока (с помощью a continueили чего-то еще) без повторной генерации исключения.

Если вы хотите продолжить цикл, что бы ни случилось, вам не нужен оператор finally: просто перехватите исключение и не вызывайте повторно.


1

finallyвыполняется независимо от того, выбрано ли неперехваченное исключение. Другие уже объяснили, почему это continueнелогично, но вот альтернатива, которая следует духу того, о чем, похоже, просит этот код. По сути, finally { continue; }это говорит:

  1. Когда будут обнаружены исключения, продолжить
  2. Если есть неперехваченные исключения, разрешите их выбросить, но продолжайте

(1) можно удовлетворить, поместив continueв конец каждого catch, а (2) можно удовлетворить, сохранив неперехваченные исключения, которые будут выброшены позже. Вы могли бы написать это так:

var exceptions = new List<Exception>();
foreach (var foo in list) {
    try {
        // some code
    } catch (InvalidOperationException ex) {
        // handle specific exception
        continue;
    } catch (Exception ex) {
        exceptions.Add(ex);
        continue;
    }
    // some more code
}
if (exceptions.Any()) {
    throw new AggregateException(exceptions);
}

Фактически, finallyон также был бы выполнен в третьем случае, где не было вообще никаких исключений, пойманных или неперехваченных. Если бы это было желательно, вы, конечно, могли бы просто разместить один continueпосле блока try-catch, а не внутри каждого catch.


1

Технически говоря, это ограничение основного CIL. Из спецификации языка :

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

и

Передача управления из защищенной области разрешается только через инструкцию исключения ( leave, end.filter, end.catchили end.finally)

На странице документации с brинструкциями :

Передача управления в блоки try, catch, filter и finally не может выполняться с помощью этой инструкции.

Последнее верно для всех инструкций ветвления, включая beq, brfalseи т. Д.


-1

Разработчики языка просто не хотели (или не могли) рассуждать о семантике блока finally, который завершается передачей управления.

Одна проблема, или, возможно, ключевая проблема, заключается в том, что finallyблок выполняется как часть некоторой нелокальной передачи управления (обработка исключений). Целью этой передачи управления не является замыкающий цикл; обработка исключения прерывает цикл и продолжает разворачиваться дальше.

Если у нас есть передача управления из finallyблока очистки, то исходная передача управления «захватывается». Он отменяется, и контроль переходит в другое место.

Семантику можно проработать. Это есть на других языках.

Разработчики C # решили просто запретить статическую передачу элементов управления, подобную goto, тем самым несколько упростив ситуацию.

Однако даже если вы это сделаете, это не решит вопрос о том, что произойдет, если динамическая передача инициируется из a finally: что, если блок finally вызывает функцию, а эта функция вызывает? Затем исходная обработка исключений «перехватывается».

Если вы проработаете семантику этой второй формы угона, не будет никаких причин для исключения первого типа. На самом деле это одно и то же: передача управления - это передача управления, независимо от того, в той же лексической области или нет.


Разница в том, что finallyпо определению сразу же должно следовать продолжение передачи, вызвавшей ее (неперехваченное исключение returnи т. Д.). Было бы легко разрешить «goto-подобные» передачи внутри finally(кстати, какие языки это делают?), Но это будет означать, что try { return } finally { ... } возврат может не произойти , что совершенно неожиданно. Если finallyвызывает вызывающую функцию, это не совсем то же самое, потому что мы уже ожидаем, что исключения могут возникнуть в любое время и прервать нормальный поток.
— nmclean

@nmclean: На самом деле, исключение, которое ускользает finally, похоже на то же самое, поскольку оно может привести к неожиданному и нелогичному потоку программы. Единственное отличие состоит в том, что разработчики языка, будучи не в состоянии отклонить все программы, в которых блок finally может вызвать необработанное исключение, вместо этого разрешают таким программам компилироваться и надеются, что программа сможет принять любые последствия, которые могут последовать за отказом от более ранней обработки исключения. последовательность.
— supercat 02

@supercat Я согласен с тем, что это нарушает ожидаемый поток, но я считаю, что это всегда имеет место для необработанных исключений, независимо от того, является ли текущий поток a finallyили нет. По логике последнего абзаца Kaz, функции также должны иметь право влиять на поток в области их вызывающей стороны посредством continueи т. Д., Поскольку им разрешено делать это посредством исключений. Но это, конечно, недопустимо; исключения являются единственным исключением из этого правила, отсюда и название «исключение» (или «прерывание»).
— nmclean 02 авг.13

@nmclean: невложенные исключения представляют собой поток управления, который отличается от обычного выполнения, но все еще структурирован. Оператор, следующий за блоком, должен выполняться только в том случае, если все исключения, произошедшие в этом блоке, были пойманы в нем. Это может показаться разумным ожиданием, но исключение, возникающее в finallyблоке, может нарушить его. Действительно неприятная ситуация, которую, ИМХО, следовало решить, как минимум сообщив finallyблокам о любых ожидающих исключениях, чтобы они могли создавать составные объекты исключений.
— supercat 02 авг.13,

@mmclean Извините, я не согласен с тем, что название «исключение» происходит от некоего «исключения из правила» (относительно контроля), специфичного для C #, LOL. Аспект исключения изменения потока управления - это просто нелокальная динамическая передача управления. Регулярная передача управления в той же лексической области (без раскрутки) может рассматриваться как частный случай этого.
— Kaz
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.