Коли нарешті запускається, якщо викинути виняток із блоку лову?


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

У наведеному вище блоці, коли називається остаточний блок? Перед викиданням е або нарешті дзвонить і потім ловити?


14
ps ви не повинні "кидати е;" тому що це зіпсує слід стека початкового винятку. Вам слід просто «кинути;». Або створіть новий виняток і встановіть InnerException на "e" перед тим, як кинути його.
Ерв ​​Вальтер

24
нарешті, був би досить поганий вибір ключового слова, якби воно не працювало останнім , чи не сказати б ви?
Ерік Ліпперт

@ErvWalter це все-таки правда? Я тестую його обома способами у VS2017, і, схоже, точно так само. Чи можете ви надати додаткову інформацію чи довідку? Дякую
Джефф Пукетт

просто називаючи пропозицію використовувати Виняток ex - резерв e для подій / делегатів
г-н R

Відповіді:


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/… : "У межах оброблюваного винятку, пов'язаний остаточний блок гарантовано запускається. Однак , якщо виняток є необробленим, виконання остаточного блоку залежить від того, як запускається операція розмотування винятку. Це, в свою чергу, залежить від налаштування комп'ютера. "
DotNetSparky

1
Зауважте, що "спійманий блок спробу / лову ще більше стека викликів" буде включати обробники фреймворків, такі як ASP.NET або тестовий запуск. Кращим способом введення цього може бути "якщо ваша програма продовжує працювати після блоку вилову, тоді нарешті блок буде виконаний".
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 ілюструє це вкладеною спробою / ловити / нарешті :)
Ален Райс,

1
@AllenRice чому б спробувати це, якщо у Марка вже є, і я можу просто гугл знайти відповідь Марка? А може, краще, спробуйте самі, тоді створіть питання ТА і відповіді на нього самостійно на благо інших.
joshden

8
Зауважте, що якщо ви не ловите виняток у зовнішньому улові, внутрішній нарешті НІКОЛИ не виконується !! У цьому випадку результатouter try inner try inner catch Unhandled Exception: System.DivideByZeroException...
Ендрю

1
@Andrew ти маєш рацію. Пояснення ви можете знайти тут MSDN Magazine 2008 вересня: Обробка необроблених винятків у CLR (щоб відкрити chm, потрібно розблокувати: Властивості файлу -> Загальне -> Розблокувати). Якщо ви заміните зовнішній блок лову на "catch (ArgumentException)", ніхто остаточно блокувати теж не буде продовжено, оскільки CLR не може знайти жодного "обробника винятків, який погодився обробляти виняток" DivideByZeroException.
Владимир

35

Прочитавши всі відповіді тут, схоже, що остаточна відповідь залежить :

  • Якщо ви повторно кидаєте виняток у блок лову, і цей виняток потрапляє всередину іншого блоку лову, все виконується відповідно до документації.

  • Однак, якщо повторне вилучення вилучення є без обробки, остаточно ніколи не виконується.

Я перевірив цей зразок коду в VS2010 w / 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: повторно киньте всередину іншого блоку спробу:
--повторіть спробу
---- внутрішній спробу
---- внутрішній ловить
---- внутрішній нарешті - нарешті
лову
--укінець нарешті
Huzzah!

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

Неопрацьоване виняток: System.Exception: Виключено тип "System.Exception".
у ConsoleApplication1.Program.Main () у C: \ local source \ ConsoleApplication1 \ Program.cs: рядок 53


3
Чудовий улов, я про це не знав!
Андрій

1
Зауважте, що остання нарешті може запуститися, залежно від того, що ви вибрали: stackoverflow.com/a/46267841/480982
Thomas Weller

1
Цікаво ... На .NET Core 2.0 остання частина працює після необробленого винятку.
Махді Гіасі

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

24

Ваш приклад поводитиметься однаково з цим кодом:

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

Як бічна примітка, якщо ви дійсно маєте на увазі throw e;(тобто кинути той самий виняток, який ви тільки що зловили), це набагато краще просто зробити throw;, оскільки це збереже початковий слід стека замість створення нового.


Я не думаю, що це правильно. Нарешті, слід знаходитись у зовнішньому блоці спробу, а не поза нею
Матвій Піграм

@MatthewPigram: Що ти маєш на увазі? Фактично, finallyблок буде запущений після catchблоку (навіть якщо блок лову повторно виключає виняток), що і намагається проілюструвати мій фрагмент.
Даніель Приден

з того, як я інтерпретую його приклад, він намагається зробити спробу ловити нарешті в іншому блоці спробу. НЕ
пробуй у

1
@MatthewPigram: У моїй відповіді взагалі немає жодної конструкції "пробуй-нарешті". Він має "спробувати", і всередині tryблоку моєї відповіді є "спробувати-улов". Я намагаюся пояснити поведінку 3-х частинної конструкції за допомогою двох 2-х частинних конструкцій. Я не бачу жодних ознак другого tryблоку в оригінальному запитанні, тому я не розумію, звідки ви це дістаєте.
Даніель Приден

12

Якщо всередині блоку обробника лову є необроблений виняток, нарешті блок називається рівно нульовим разів

  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: \ користувачів \ адміністратор \ документи \ TestExceptionNesting \ bin \ Release> TestExceptionNesting.exe

у спробі

в улові

Неопрацьоване виняток: System.DivideByZeroException: Спроба розділити на нуль. у TestExceptionNesting.Program.Main (String [] аргументи) в C: \ users \ administrator \ документи \ TestExceptionNesting \ TestExceptionNesting.cs: рядок 22

C: \ користувачів \ адміністратор \ документи \ TestExceptionNesting \ bin \ release>

Мені сьогодні було задано це питання під час інтерв'ю, і інтерв'юер продовжував повертатися назад "ти впевнений, що нарешті не телефонуєш?" Я був невпевнений, чи малося на увазі підступне запитання чи інтерв'юер мав на увазі щось інше і написав неправильний код для налагодження, тому я прийшов додому і спробував це (побудувати та запустити, ніякої взаємодії з налагодженням), просто щоб подумати про це відпочинок.


Якщо викинутий виняток не потрапляє в інший улов десь вище стека, у такому випадку він може виконатись, якщо цей викинутий виняток обробляється ... Або я можу помилятися ...
tomosius

@tomosius, так, саме так пояснюється відповідь Брендона. :)
Андрій

@tomosius Саме тому я почав із зазначення "Якщо є необроблений виняток". Якщо викинутий виняток десь спійманий, то за визначенням ми говоримо про інший випадок.
Eusebio Rufian-Zilbermann

Це не правда. Принаймні, не для NET Core 3.1. Простий новий проект консолі з цим кодом показує "нарешті" після винятку.
emzero

Цікаво, що поведінка змінилася, .NET core навіть не існував, коли я публікував;)
Eusebio Rufian-Zilbermann

2

Простий спосіб сказати також - налагодити свій код і помітити, коли нарешті викликається.


1

Тестуючи програму консолі C #, остаточний код був виконаний після того, як викинутий виняток: "Діалог помилок програми" існував і після того, як ви вибрали опцію "Закрити програму", нарешті блок був виконаний у цьому вікні консолі. Але встановивши точку злому всередині остаточно кодового блоку, я ніколи не можу його вдарити. Відладчик постійно зупиняється на заяві про кидок. Ось мій тестовий код:

    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.