Когда, наконец, запустится, если вы выбросите исключение из блока catch?


135
try {
   // Do stuff
}
catch (Exception e) {
   throw;
}
finally {
   // Clean up
}

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


14
ps не стоит "бросать е;" потому что это испортит трассировку стека исходного исключения. Вы должны просто «выбросить». Или создайте новое исключение и установите для InnerException значение «e» перед тем, как его выбросить.
— Эрв Вальтер,

24
наконец, было бы довольно плохим выбором ключевого слова, если бы оно не запускалось последним , не так ли?
— Эрик Липперт,

@ErvWalter, это все еще правда? Я тестирую его в обоих направлениях в VS2017, и он выглядит точно так же. Не могли бы вы предоставить дополнительную информацию или ссылку? Спасибо
— Джефф Пакетт

просто предложение именования используйте Exception ex - зарезервировать e для событий / делегатов
— н Р

Ответы:


138

Он будет вызываться после повторного вызова e (то есть после выполнения блока catch)

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


18
и никогда, если вы вызовете Envrionment.FailFast ()
— Йоханнес Рудольф

16
После попытки код в ответ Брендона, я вижу , что finallyне выполняется , если исключение брошено в предыдущем catchникогда не пойманы во внешнем try- catchблок!
— Эндрю

3
Благодарим вас за редактирование вашего (принятого) ответа с целью включения новой информации.
— Гордон Бин,

3
Они (Microsoft) говорят об этом на новом сайте документации: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… : «В обработанном исключении связанный блок finally гарантированно запускается. Однако , если исключение не обрабатывается, выполнение блока finally зависит от того, как запускается операция очистки исключения. Это, в свою очередь, зависит от того, как настроен ваш компьютер ".
— DotNetSparky

1
Обратите внимание, что «пойманный блоком try / catch дальше по стеку вызовов» будет включать обработчики фреймворка, такие как в ASP.NET или средство выполнения тестов. Лучше всего это выразить так: «если ваша программа продолжает выполняться после блока catch, то будет выполнен блок finally».
— ArrowCase

91

Почему бы не попробовать:

outer try
inner try
inner catch
inner finally
outer catch
outer finally

с кодом (отформатированным для вертикального пространства):

static void Main() {
    try {
        Console.WriteLine("outer try");
        DoIt();
    } catch {
        Console.WriteLine("outer catch");
        // swallow
    } finally {
        Console.WriteLine("outer finally");
    }
}
static void DoIt() {
    try {
        Console.WriteLine("inner try");
        int i = 0;
        Console.WriteLine(12 / i); // oops
    } catch (Exception e) {
        Console.WriteLine("inner catch");
        throw e; // or "throw", or "throw anything"
    } finally {
        Console.WriteLine("inner finally");
    }
}

5
+1, для чего-то настолько простого, вам действительно стоит просто попробовать, как это сделал Марк. GJ иллюстрирует это вложенным try / catch / finally :)
— Аллен Райс

1
@AllenRice, зачем пробовать, если Марк уже сделал это, и я могу просто погуглить, чтобы найти ответ Марка? Или, может быть, лучше, попробуйте себя, а затем создайте вопрос SO и ответьте на него самостоятельно для других.
— joshden

8
Обратите внимание: если вы не поймаете исключение во внешнем улове, внутреннее, наконец, НИКОГДА не выполняется !! В этом случае на выходе получаетсяouter try inner try inner catch Unhandled Exception: System.DivideByZeroException...
— Эндрю

1
@ Андрей, ты прав. Объяснение вы можете найти здесь MSDN Magazine 2008 Сентябрь: Обработка необработанных исключений в CLR (чтобы открыть chm, необходимо разблокировать: Свойства файла -> Общие -> Разблокировать). Если вы замените внешний блок catch на «catch (ArgumentException)», ни один блок finally также не будет обработан, потому что CLR не может найти никакой «обработчик исключений, который согласился обработать исключение« DivideByZeroException ».
— Владимир

35

После прочтения всех ответов здесь, похоже , окончательный ответ зависит :

  • Если вы повторно генерируете исключение в блоке catch, и это исключение перехватывается внутри другого блока catch, все выполняется в соответствии с документацией.

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

Я тестировал этот образец кода в VS2010 с C # 4.0

static void Main()
    {
        Console.WriteLine("Example 1: re-throw inside of another try block:");

        try
        {
            Console.WriteLine("--outer try");
            try
            {
                Console.WriteLine("----inner try");
                throw new Exception();
            }
            catch
            {
                Console.WriteLine("----inner catch");
                throw;
            }
            finally
            {
                Console.WriteLine("----inner finally");
            }
        }
        catch
        {
            Console.WriteLine("--outer catch");
            // swallow
        }
        finally
        {
            Console.WriteLine("--outer finally");
        }
        Console.WriteLine("Huzzah!");

        Console.WriteLine();
        Console.WriteLine("Example 2: re-throw outside of another try block:");
        try
        {
            Console.WriteLine("--try");
            throw new Exception();
        }
        catch
        {
            Console.WriteLine("--catch");
            throw;
        }
        finally
        {
            Console.WriteLine("--finally");
        }

        Console.ReadLine();
    }

Вот вывод:

Пример 1: повторный бросок внутрь другого блока try:
--outer try
---- внутренняя попытка
---- внутренняя ловушка
---- внутренняя, наконец -
внешняя
ловушка --outer finally Huzzah
!

Пример 2: повторный выброс за пределами другого блока try:
--try
--catch

Необработанное исключение: System.Exception: возникло исключение типа «System.Exception».
в ConsoleApplication1.Program.Main () в C: \ local source \ ConsoleApplication1 \ Program.cs: строка 53


3
Отличный улов, я не знал об этом!
— Андрей

1
Обратите внимание, что последнее, наконец, может работать, в зависимости от того, что вы выберете: stackoverflow.com/a/46267841/480982
— Томас Веллер

1
Интересно ... В .NET Core 2.0 часть finally выполняется после необработанного исключения.
— Mahdi Ghiasi

Интересно, что я только что провел тест как в .NET Core 2.0, так и в .NET Framework 4.6.1, и оба они запускают finally после необработанного исключения. Изменилось ли это поведение?
— Кэмерон Бильштейн,

24

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

try {
    try {
        // Do stuff
    } catch(Exception e) {
        throw e;
    }
} finally {
    // Clean up
}

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


Я не думаю, что это правильно. Наконец, он должен быть внутри внешнего блока попыток, а не за его пределами
— Мэтью Пигрэм

@MatthewPigram: Что ты имеешь в виду? finallyБлок фактически будет работать после catchблока (даже если блок поймать rethrows исключения), что и мой фрагмент кода пытается проиллюстрировать.
— Дэниел Прайден

судя по тому, как я интерпретирую его пример, он, наконец, пытается сделать попытку улова в другом блоке попытки. НЕ пробный улов внутри пробного улова, наконец,
— Мэтью Пигрэм

1
@MatthewPigram: В моем ответе вообще нет конструкции «попробуй-поймай-наконец». В нем есть «попробуй-наконец», а внутри этого tryблока в моем ответе есть «попробуй-поймай». Я пытаюсь объяснить поведение конструкции из трех частей, используя две конструкции из двух частей. Я не вижу никаких признаков второго tryблока в исходном вопросе, поэтому я не понимаю, откуда вы это взяли.
— Дэниел Прайден

12

Если внутри блока обработчика catch есть необработанное исключение, блок finally вызывается ровно ноль раз.

  static void Main(string[] args)
  {
     try
     {
        Console.WriteLine("in the try");
        int d = 0;
        int k = 0 / d;
     }
     catch (Exception e)
     {
        Console.WriteLine("in the catch");
        throw;
     }
     finally
     {
        Console.WriteLine("In the finally");
     }
  }

Вывод:

C: \ Users \ Администратор \ Documents \ TestExceptionNesting \ Bin \ Release> TestExceptionNesting.exe

в попытке

в улове

Необработанное исключение: System.DivideByZeroException: попытка разделить на ноль. в TestExceptionNesting.Program.Main (String [] args) в C: \ users \ administrator \ documents \ TestExceptionNesting \ TestExceptionNesting.cs: строка 22

C: \ Users \ Администратор \ Documents \ TestExceptionNesting \ Bin \ выпуск>

Мне задали этот вопрос сегодня на интервью, и интервьюер все время повторял: «Вы уверены, что наконец-то не позвонили?» Я не был уверен, имел ли это в виду вопрос с подвохом, или интервьюер имел в виду что-то еще и написал мне неправильный код для отладки, поэтому я пришел домой и попробовал его (сборка и запуск, без взаимодействия с отладчиком), просто чтобы сосредоточиться на остальное.


Если выброшенное исключение не попадает в другую ловушку где-то выше по стеку, в этом случае оно может выполняться, если это выброшенное исключение обработано ... Или я могу ошибаться ...
— tomosius

@tomosius, да, это объясняет ответ Брэндона. :)
— Андрей

@tomosius Вот почему я начал с указания «Если есть необработанное исключение». Если выброшенное исключение где-то обнаружено, то по определению мы говорим о другом случае.
— Эусебио Руфиан-Зильберманн

Это неправда. По крайней мере, для NET Core 3.1. Простой новый консольный проект с этим кодом отображается «в конце» после исключения.
— emzero

Интересно, что поведение изменилось, ядро ​​.NET даже не существовало, когда я
— писал

2

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


1

При тестировании с консольным приложением C # код finally был выполнен после того, как возникло исключение: существовал «диалог ошибки приложения», и после того, как вы выбрали опцию «Закрыть программу», в этом окне консоли был выполнен блок finally. Но установив точку разрыва внутри блока кода finally, я никогда не смогу попасть в нее. Отладчик продолжает останавливаться на инструкции throw. Вот мой тестовый код:

    class Program
    {
       static void Main(string[] args)
       {
          string msg;
          Console.WriteLine(string.Format("GetRandomNuber returned: {0}{1}", GetRandomNumber(out msg), msg) == "" ? "" : "An error has occurred: " + msg);
       }

       static int GetRandomNumber(out string errorMessage)
       {
         int result = 0;
         try
         {
            errorMessage = "";
            int test = 0;
            result = 3/test;
            return result;
         }
         catch (Exception ex)
         {
            errorMessage = ex.Message;
            throw ex;

         }
         finally
         {
            Console.WriteLine("finally block!");
         }

       }
    }

Отладка в VS2010 - .NET Framework 4.0

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.