C#. Стратегия использования блоков catch

Использование нескольких блоков catch

В этой статье я вновь хочу коснуться темы обработки исключительных ситуаций, в программах, написанных на C#. Точнее, я хочу рассказать о нескольких нюансах, связанных с обработкой ошибок, а именно, с использованием блоков catch (блоков обработки ошибок). Как мы помним, основная идея подхода к обработке исключительных ситуаций, заключается в разделении программного кода на две части, одна представляет собой потенциально опасный блок кода (в ней могут происходить ошибки), а другая отвечает за обработку произошедших ошибок. Схематично это может выглядеть так:

try
{
    //Потенциально опасный блок кода
}
catch (тип_ошибок)
{
    //Обработка ошибок
}

Это самый простой вариант использования блоков try..catch, в нем содержится только один блок обработки ошибок. Но даже в таком варианте, эта схема рабочая. Чтобы обрабатывать всё возможные исключительные ситуации, мы должны указать самый «общий» тип ошибок в круглых скобках блока catch.

В C#, самым «общим» типом (классом) исключений является «Exception» из пространства имен «System».

В прошлой статье, я рассказывал что все классы-исключения должны быть унаследованы от класса «Exception», и это значит что если мы хотим обрабатывать все ошибки в одном блоке catch, мы должны написать примерно такой код:

try
{
    //Потенциально опасный блок кода
}
catch (Exception ex)
{
    //Обработка ошибок
}

С одной стороны, это дает нам гарантию, что любая ошибка будет обработана, но у такого подхода есть и существенный минус — мы не знаем какого типа ошибка будет обрабатываться в каждом конкретном случае, и не сможем её оптимально отработать. Так что, на практике, почти всегда используются несколько блоков catch для одного блока try.

Вот тут нужно знать об одном нюансе: блок catch с наиболее общего типа должен следовать самым последним. А всё это по тому, что при возникновении ошибки в блоке try, система начинает «искать» блоки catch, и найдя очередной блок, сравнивает тип ошибок, обрабатываемый им с типом сгенерированного объекта-исключения, и как только система находит первое соответствие, то она передает обработку в этот самый блок catch. Но суть тут в том, что система ищет не только прямые совпадения тип в тип, например, объект-исключение имеет тип «ArgumentException» и в блоке catch указан тип «ArgumentException», но и косвенные совпадения. Под косвенными совпадениями я подразумеваю возможность приведения типов по иерархии наследования, т.е. объект типа «ArgumentException» может восприниматься и как объект типа «Exception», так как класс «ArgumentException» является наследником класса «Exception». И код представленный ниже, не будет  эффективным:

//Неэффективная обработка исключительных ситуаций
try
{
    //Потенциально опасный участок кода
}
catch (Exception ex)
{
    //Блок обработки ошибок общего типа
}
catch (ArgumentException ex)
{
    //Блок обработки специфических ошибок
}

В примере, показанном выше, при возникновении ошибки в блоке try, её обработка будет передана в блок «catch (Exception ex)», даже если тип ошибки будет «ArgumentException», и дело не дойдет до блока «catch (ArgumentException ex)». Так как объект типа «ArgumentException» просто успешно приведется к типу «Exception» и будет обработан как таковой. Для решения подобной проблемы нужно поменять местами блоки catch:

try
{
    //Потенциально опасный участок кода
}
catch (ArgumentException ex) 
{ 
    //Блок обработки специфических ошибок 
}
catch (Exception ex)
{
    //Блок обработки ошибок общего типа
}

Вот и всё. Теперь, при возникновении ошибки типа «ArgumentException» она будет обрабатываться специальным блоком catch, а все остальные типы ошибок пройдут дальше в общий блок обработки. А на практике, блок «catch (Exception ex)» располагают последним в списке, и используют для обработки непредсказуемых ошибок, как правило, чтобы сказать пользователю, что программа дальше работать не может…

Добавить комментарий