Если код, который вызывает исключение, не размещен в блоке try или помещен в конструкцию try..catch, которая не содержит соответствующего блока catch для обработки возникшего исключения, то система производит поиск соответствующего обработчика исключения в стеке вызовов.
Например, рассмотрим следующую программу:
try { TestClass.Method1(); } catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); } Console.WriteLine("Конец метода Main"); class TestClass { public static void Method1() { try { Method2(); } catch (IndexOutOfRangeException ex) { Console.WriteLine($"Catch в Method1 : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Method1"); } Console.WriteLine("Конец метода Method1"); } static void Method2() { try { int x = 8; int y = x / 0; } finally { Console.WriteLine("Блок finally в Method2"); } Console.WriteLine("Конец метода Method2"); } }
В данном случае стек вызовов выглядит следующим образом: метод Main вызывает метод Method1, который, в свою очередь, вызывает метод Method2. И в методе Method2 генерируется исключение DivideByZeroException. Визуально стек вызовов можно представить следующим образом:
Внизу стека метод Main, с которого началось выполнение, и на самом верху метод Method2.
Что будет происходить в данном случае при генерации исключения?
Метод Main вызывает метод Method1, а тот вызывает метод Method2, в котором генерируется исключение DivideByZeroException.
Система видит, что код, который вызывал исключение, помещен в конструкцию try..catch
try { int x = 8; int y = x / 0; } finally { Console.WriteLine("Блок finally в Method2"); }
Система ищет в этой конструкции блок catch, который обрабатывает исключение DivideByZeroException. Однако такого блока catch нет.
Система опускается в стеке вызовов в метод Method1, который вызывал Method2. Здесь вызов Method2 помещен в конструкцию try..catch
try { Method2(); } catch (IndexOutOfRangeException ex) { Console.WriteLine($"Catch в Method1 : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Method1"); }
Система также ищет в этой конструкции блок catch, который обрабатывает исключение DivideByZeroException. Однако здесь также подобный блок catch отсутствует.
Система далее опускается в стеке вызовов в метод Main, который вызывал Method1. Здесь вызов Method1 помещен в конструкцию try..catch
try { TestClass.Method1(); } catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); }
Система снова ищет в этой конструкции блок catch, который обрабатывает исключение DivideByZeroException. И в данном случае такой блок найден.
Система наконец нашла нужный блок catch в методе Main, для обработки исключения, которое возникло в методе Method2 - то есть к начальному методу, где непосредственно возникло исключение. Но пока данный блок catch НЕ выполняется. Система поднимается обратно по стеку вызовов в самый верх в метод Method2 и выполняет в нем блок finally:
finally { Console.WriteLine("Блок finally в Method2"); }
Далее система возвращается по стеку вызовов вниз в метод Method1 и выполняет в нем блок finally:
finally { Console.WriteLine("Блок finally в Method1"); }
Затем система переходит по стеку вызовов вниз в метод Main и выполняет в нем найденный блок catch и последующий блок finally:
catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); }
Далее выполняется код, который идет в методе Main после конструкции try..catch:
Console.WriteLine("Конец метода Main");
Стоит отметить, что код, который идет после конструкции try...catch в методах Method1 и Method2, не выполняется, потому что обработчик исключения найден именно в методе Main.
Консольный вывод программы:
Блок finally в Method2 Блок finally в Method1 Catch в Main: Attempted to divide by zero Блок finally в Main Конец метода Main