Поиск блока catch при обработке исключений


Если код, который вызывает исключение, не размещен в блоке 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. Визуально стек вызовов можно представить следующим образом:

Поиск блока catch при обработке исключения в C#
Внизу стека метод 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