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

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

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

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

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

Почему?

Ответ 1

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

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

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

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

C#, you, and the out if success

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

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

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

Ответ 2

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

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

Это взято из MSDN, 8.9.2 Инструкция continue.

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

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

Это отсюда 8.10 Инструкция try.

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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;
}

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

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

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

The Dreaded Goto

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. Разработчики языка чувствовали, что это слишком неоднозначно и (вероятно) невозможно гарантировать во время компиляции, что ваш continue используется только в тех случаях, когда поток управления не передается.

Ответ 5

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

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

Ответ 6

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

Ну, я думаю, что нет.

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

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

Ответ 7

Вы не можете оставить тело блока finally. Это включает в себя разрыв, возврат и в вашем случае продолжение ключевых слов.

Ответ 8

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

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

Ответ 9

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

  • Если есть исключения, продолжите
  • Когда есть неотображаемые исключения, разрешите их бросать, но продолжайте

(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.

Ответ 10

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

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

и

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

На странице doc для br инструкция:

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

Последнее значение сохраняется для всех команд ветвления, включая beq, brfalse и т.д.

Ответ 11

Поскольку Python 3.8 continue в блоке finally разрешен.

Заявление продолжения было незаконным в предложении finally из-за проблема с реализацией. В Python 3.8 это ограничение было поднял. (Внесено Сергеем Сторчака в bpo-32489.)

https://docs.python.org/3.8/whatsnew/3.8.html

Ответ 12

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

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

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

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

Дизайнеры С# решили просто запретить статические, "переходные" передачи управления, тем самым несколько упростив некоторые вещи.

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

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