// Прежде чем продолжить, проверим допустимость аргумента.
public void Accelerate (int delta) {
if (delta ‹ 0) throw new ArgumentOutOfRangeException("Скорость должна быть выше нуля!");
}
Рис. 6.5. Шаблон программного кода Exception
Логика catch должна соответствовать каждому типу исключений.
static void Main(string [] args) {
…
// Здесь учитывается множество исключений.
try {
for (int i = 0; i ‹ 10; i++) myCar.Accelerate(10);
} catch(CarIsDeadExeeption e) {
// Обработка CarIsDeadException.
} catch (ArgumentOutOfRangeException e) {
// Обработка ArgumentOutOfRangeException.
}
При создании множества блоков catch следует учитывать то, что сгенерированное исключение будет обработано "первым подходящим" бликом catch. Чтобы понять, что такое "первый подходящий" блок catch, добавьте в предыдущий фрагмент программного кода еще один блок catch, который будет обрабатывать все исключения после CarIsDeadException и ArgumentOutOfRangeException, выполняя захват System.Exception общего вида, как показано ниже.
// Этот программный код не компилируется!
static void Main(string[] args) {
…
try {
for (int i = 0; i ‹ 10; i++) myCar.Accelerate(10);
} catch(Exception e) {
// Обработка всех остальных исключений?
} catch(CarIsDeadException e) {
// Обработка CarIsDeadException.
} catch(ArgumentOutOfRangeException e) {
// Обработка ArgumentOutOfRangeException.
}
…
}
Такая логика обработки исключений порождает ошибки компиляции. Проблема в том, что первый блок catch может обработать все , что оказывается производным от System.Exception, включая типы CarIsDeadException и ArgumentOutOfRangeException. Таким образом, оставшиеся два блока catch оказываются просто недостижимыми!
Правило, которое следует использовать на практике, заключается в необходимости размещать блоки catch так, чтобы первый блок соответствовал самому "частному" исключению (т.е. самому младшему производному типу в цепочке наследования), а последний блок – самому "общему" (т.е. базовому классу данной цепочки, в данном случае это System.Exception).
Поэтому если вы хотите определить оператор catch, который обработает все ошибки после CarIsDeadException и ArgumentOutOfRangeException, вы должны записать следующее.
// Этот программный код будет скомпилирован.
static void Main(string[] args) {
…
try {
for (int i = 0; i ‹ 10; i++) myCar.Accelerate(10);
} catch(CarIsDeadException e) {
// Обработка CarIsDeadException.
} catch(ArgumentOutOfRangeException) {
// Обработка ArgumentOutOfRangeException.
} catch (Exception e) {
// Здесь будут обработаны все остальные возможные исключения,
// генерируемые операторами в рамках try.
}
…
}
В C# также поддерживается "общий" блок catch, который не получает явно объект исключения, генерируемый данным членом.
// Блок catch общего вида.
static void Main(string[] args) {
…
try {
for (int i = 0; i ‹ 10; i++) myCar.Accelerate(10);
} catch {
Console.WriteLine("Случилось что-то ужасное…");
}
…
}
Очевидно, что это не самый информативный способ обработки исключения, поскольку здесь вы не имеете возможности получить содержательную информацию о произошедшей ошибке (например, имя метода, содержимое стека вызовов или пользовательское сообщение). Тем не менее, в C# такая конструкция возможна.
Генерирование вторичных исключений
Вы должны знать, что в рамках логики try имеется возможность генерировать исключение для вызывающей стороны, находящейся выше по цепочке вызовов в стеке вызовов. Для этого просто используйте ключевое слово throw в рамках блока сatch. Тем самым исключение будет направлено выше по цепочке логики вызовов, что может быть полезно тогда, когда данный блок catch не имеет возможности полностью обработать возникшую ошибку.
// Перекладывание ответственности.
static void Main (string[] args) {
…
try {
// Логика ускорения автомобиля…}
catch(CarIsDeadException e) {
// Частичная обработка ошибки и перенаправление.
// Здесь перенаправляется входной объект CarIsDeadException.
// Но можно генерировать и другое исключение.
throw e;
Читать дальше