Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 101 102 103 104 105 106 107 108 109 ... 407
(которым будет считаться любое значение меньше нуля). Обратите внимание, что конструктор этого класса исключения принимает имя проблемного аргумента в первом параметре типа string, за которым следует сообщение с описанием ошибки.

// Перед продолжением проверить аргумент на предмет допустимости.

public void Accelerate(int delta)

{

  if (delta < 0)

  {

    throw new ArgumentOutOfRangeException(nameof(delta),

      "Speed must be greater than zero");

    // Значение скорости должно быть больше нуля!

  }

  ...

}

На заметку! Операция nameof() возвращает строку, представляющую имя объекта, т.е. переменную delta в рассматриваемом примере. Такой прием позволяет безопасно ссылаться на объекты, методы и переменные С#, когда требуются их строковые версии.

Теперь логика в блоке catch может реагировать на каждый тип исключения специфическим образом:

using System;

using System.IO;

using ProcessMultipleExceptions;

Console.WriteLine("***** Handling Multiple Exceptions *****n");

Car myCar = new Car("Rusty", 90);

try

{

  // Вызвать исключение выхода за пределы диапазона аргумента.

  myCar.Accelerate(-10);

}

catch (CarIsDeadException e)

{

  Console.WriteLine(e.Message);

}

catch (ArgumentOutOfRangeException e)

{

  Console.WriteLine(e.Message);

}

Console.ReadLine();

При написании множества блоков catch вы должны иметь в виду, что когда исключение сгенерировано, оно будет обрабатываться первым подходящим блоком catch. Чтобы проиллюстрировать, что означает "первый подходящий" блок catch, модифицируйте предыдущий код, добавив еще один блок catch, который пытается обработать все остальные исключения кроме CarIsDeadException и ArgumentOutOfRangeException путем перехвата общего типа System.Exception:

// Этот код не скомпилируется!

Console.WriteLine("***** Handling Multiple Exceptions *****n");

Car myCar = new Car("Rusty", 90);

try

{

  // Вызвать исключение выхода за пределы диапазона аргумента.

  myCar.Accelerate(-10);

}

catch(Exception e)

{

  // Обработать все остальные исключения?

  Console.WriteLine(e.Message);

}

catch (CarIsDeadException e)

{

  Console.WriteLine(e.Message);

}

catch (ArgumentOutOfRangeException e)

{

  Console.WriteLine(e.Message);

}

Console.ReadLine();

Представленная выше логика обработки исключений приводит к возникновению ошибок на этапе компиляции. Проблема в том, что первый блок catch способен обрабатывать любые исключения, производные от System.Exception (с учетом отношения "является"), в том числе CarIsDeadException и ArgumentOutOfRangeException. Следовательно, два последних блока catch в принципе недостижимы!

Запомните эмпирическое правило: блоки catch должны быть структурированы так, чтобы первый catch перехватывал наиболее специфическое исключение (т.е. производный тип, расположенный ниже всех в цепочке наследования типов исключений), а последний catch — самое общее исключение (т.е. базовый класс имеющейся цепочки наследования: System.Exception в данном случае).

Таким образом, если вы хотите определить блок catch, который будет обрабатывать любые исключения помимо CarIsDeadException и ArgumentOutOfRangeException, то можно было бы написать следующий код:

// Этот код скомпилируется без проблем.

Console.WriteLine("***** Handling Multiple Exceptions *****n");

Car myCar = new Car("Rusty", 90);

try

{

  // Вызвать исключение выхода за пределы диапазона аргумента.

  myCar.Accelerate(-10);

}

catch (CarIsDeadException e)

{

  Console.WriteLine(e.Message);

}

catch (ArgumentOutOfRangeException e)

{

  Console.WriteLine(e.Message);

}

// Этот блок будет перехватывать все остальные исключения.

// помимо CarIsDeadException и ArgumentOutOfRangeException

catch (Exception e)

{

  Console.WriteLine(e.Message);

}

Console.ReadLine();

На заметку! Везде, где только возможно, отдавайте предпочтение перехвату специфичных классов исключений, а не общего класса System.Exception. Хотя может показаться, что это упрощает жизнь в краткосрочной перспективе (поскольку охватывает все исключения, которые пока не беспокоят), в долгосрочной перспективе могут возникать странные аварийные отказы во время выполнения, т.к. в коде не была предусмотрена непосредственная обработка более серьезной ошибки. Не забывайте, что финальный блок catch, который работает с System.Exception, на самом деле имеет тенденцию быть чрезвычайно общим.

Общие операторы catch

В языке C# также поддерживается "общий" контекст catch, который не получает явно объект исключения, сгенерированный заданным членом:

// Общий оператор catch.

Console.WriteLine("***** Handling Multiple Exceptions *****n");

Car myCar = new Car("Rusty", 90);

try

{

  myCar.Accelerate(90);

}

catch

{

  Console.WriteLine("Something bad happened...");

                  // Произошло что-то плохое...

}

Console.ReadLine();

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

Повторная генерация исключений

Внутри логики блока try перехваченное исключение разрешено повторно сгенерировать для передачи вверх по стеку вызовов предшествующему вызывающему коду. Для этого просто используется ключевое слово throw в блоке catch. В итоге исключение передается вверх по цепочке вызовов, что может оказаться полезным, если блок catch способен обработать текущую ошибку только частично:

// Передача ответственности.

...

try

{

  // Логика увеличения скорости автомобиля...

}

catch(CarIsDeadException e)

{

  // Выполнить частичную обработку этой ошибки и передать ответственность.

  throw;

}

...

Имейте в виду, что в данном примере кода конечным получателем исключения CarIsDeadException будет исполняющая среда .NET 5, т.к. операторы верхнего уровня генерируют его повторно. По указанной причине конечному пользователю будет отображаться системное диалоговое окно с информацией об ошибке. Обычно вы будете повторно генерировать частично обработанное исключение для передачи вызывающему коду, который имеет возможность обработать входное исключение более элегантным образом.

Также обратите внимание на неявную повторную генерацию объекта CarIsDeadException с помощью ключевого слова throw без аргументов. Дело в том, что здесь не создается новый объект исключения, а просто передается исходный объект исключения (со всей исходной информацией). Это позволяет сохранить контекст первоначального целевого объекта.

Внутренние исключения

Как нетрудно догадаться, вполне возможно, что исключение сгенерируется во время обработки другого исключения. Например, пусть вы обрабатываете исключение CarIsDeadException внутри отдельного

1 ... 101 102 103 104 105 106 107 108 109 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий