Подтвердить что ты не робот

Почему порядок выполнения внутренних "окончательных" и внешних "когда" заменен на С# 6.0?

Я видел этот пример:

static void Main(string[] args)
{
    Console.WriteLine("Start");
    try
    {
        SomeOperation();
    }
    catch (Exception) when (EvaluatesTo())
    {
        Console.WriteLine("Catch");
    }
    finally
    {
        Console.WriteLine("Outer Finally");
    }
}

private static bool EvaluatesTo()
{
    Console.WriteLine($"EvaluatesTo: {Flag}");
    return true;
}

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

Что производит следующий вывод:

Start
EvaluatesTo: True
Inner Finally
Catch
Outer Finally

Это звучит странно для меня, и я ищу хорошее объяснение этого порядка, чтобы обернуть его мне в голову. Я ожидал, что блок finally будет выполнен до when:

Start
Inner Finally
EvaluatesTo: True
Catch
Outer Finally

В документации указано, что этот порядок выполнения правилен, но он не уточняет, почему это делается так и что именно являются правилами порядка выполнения здесь.

4b9b3361

Ответ 1

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

Из спецификации ECMA CLR (ECMA-335, I.12.4.2.5 Обзор обработки исключений):

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

  • Защищает регион, включая текущий указатель инструкции, и
  • Является блоком обработчика catch и
  • Чей фильтр хочет обработать исключение

Если совпадение не найдено в текущем методе, выполняется поиск метода вызова и т.д. Если совпадение не найдено, CLI удалит трассировку стека и прервет работу программы.

Если совпадение найдено, CLI переводит стек обратно в только что находящуюся точку, но на этот раз вызывает обработчики finally и fault. Затем он запускает соответствующий обработчик исключений.

Как вы можете видеть, поведение на 100% соответствует спецификации.

  • Найдите защищенный блок - try в SomeOperation
  • Есть ли у него блок обработчика catch? Нет.
  • Найдите защищенный блок в вызывающем методе - try в Main
  • Есть ли у него блок обработчика catch? Да!
  • Может ли фильтр обрабатывать исключение? Фильтр оценивается (отказ от ответственности: это не означает, что все фильтры в защищенном блоке всегда будут оцениваться - не проблема, если фильтр не имеет побочных эффектов, чего он действительно не должен, конечно), и результат да.
  • Пройдите обратно в стек и выполните все обработчики finally и fault
    • finally в SomeOperation

finally в Main не является частью этого, конечно же - он будет выполняться, когда выполнение выходит из защищенного блока, независимо от исключения.

EDIT:

Просто для полноты - это всегда было так. Единственное, что изменилось, это то, что С# теперь поддерживает фильтры исключений, что позволяет вам наблюдать за порядком выполнения. VB.NET поддерживает фильтры исключений из версии 1.

Ответ 2

Блок

A finally всегда выполняет то, генерируется ли исключение.

Блок finally также выполняет:

  • После завершения блока catch
  • После того, как элемент управления покидает блок try из-за инструкции перехода (например, return или goto)
  • После завершения блока try

Единственные вещи, которые могут победить окончательный блок, - это бесконечный цикл или процесс, отправляющийся внезапно. Блок finally помогает добавить детерминизм в программу