P3 and P4 are pointing to the same object: False
Резюме
В настоящей главе объяснялась роль и детали наследования и полиморфизма. В ней были представлены многочисленные новые ключевые слова и лексемы для поддержки каждого приема. Например, вспомните, что с помощью двоеточия указывается родительский класс для создаваемого типа. Родительские типы способны определять любое количество виртуальных и/или абстрактных членов для установления полиморфного интерфейса. Производные типы переопределяют эти члены с применением ключевого слова override.
Вдобавок к построению множества иерархий классов в главе также исследовалось явное приведение между базовыми и производными типами. В завершение главы рассматривались особенности главного родительского класса в библиотеках базовых классов .NET Core — System.Object.
Глава 7
Структурированная обработка исключений
В настоящей главе вы узнаете о том, как иметь дело с аномалиями, возникающими во время выполнения кода С#, с использованием структурированной обработки исключений. Будут описаны не только ключевые слова С#, предназначенные для этих целей (try, catch, throw, finally, when), но и разница между исключениями уровня приложения и уровня системы, а также роль базового класса System.Exception. Кроме того, будет показано, как создавать специальные исключения, и рассмотрены некоторые инструменты отладки в Visual Studio, связанные с исключениями.
Ода ошибкам, дефектам и исключениям
Что бы ни нашептывало наше (порой завышенное) самомнение, идеальных программистов не существует. Разработка программного обеспечения является сложным делом, и из-за такой сложности довольно часто даже самые лучшие программы поставляются с разнообразными проблемами. В одних случаях проблема возникает из-за "плохо написанного" кода (например, по причине выхода за границы массива), а в других — из-за ввода пользователем некорректных данных, которые не были учтены в кодовой базе приложения (скажем, когда в поле для телефонного номера вводится значение вроде Chucky). Вне зависимости от причин проблемы в конечном итоге приложение не работает ожидаемым образом. Чтобы подготовить почву для предстоящего обсуждения структурированной обработки исключений, рассмотрим три распространенных термина, которые применяются для описания аномалий.
• Дефекты. Выражаясь просто, это ошибки, которые допустил программист. В качестве примера предположим, что вы программируете на неуправляемом C++. Если вы забудете освободить динамически выделенную память, что приводит к утечке памяти, тогда получите дефект.
• Пользовательские ошибки. С другой стороны, пользовательские ошибки обычно возникают из-за тех, кто запускает приложение, а не тех, кто его создает. Например, ввод конечным пользователем в текстовом поле неправильно сформированной строки с высокой вероятностью может привести к генерации ошибки, если в коде не была предусмотрена обработка некорректного ввода.
• Исключения. Исключениями обычно считаются аномалии во время выполнения, которые трудно (а то и невозможно) учесть на стадии программирования приложения. Примерами исключений могут быть попытка подключения к базе данных, которая больше не существует, открытие запорченного XML-файла или попытка установления связи с машиной, которая в текущий момент находится в автономном режиме. В каждом из упомянутых случаев программист (или конечный пользователь) обладает довольно низким контролем над такими "исключительными" обстоятельствами.
(window.adrunTag = window.adrunTag || []).push({v: 1, el: 'adrun-4-390', c: 4, b: 390})
С учетом приведенных определений должно быть понятно, что структурированная обработка исключений в .NET — прием работы с исключительными ситуациями во время выполнения. Тем не менее, даже для дефектов и пользовательских ошибок, которые ускользнули от глаз программиста, исполняющая среда будет часто генерировать соответствующее исключение, идентифицирующее возникшую проблему. Скажем, в библиотеках базовых классов .NET 5 определены многочисленные исключения, такие как FormatException, IndexOutOfRangeException, FileNotFoundException, ArgumentOutOfRangeException и т.д.
В рамках терминологии .NET исключение объясняется дефектами, некорректным пользовательским вводом и ошибками времени выполнения, даже если программисты могут трактовать каждую аномалию как отдельную проблему. Однако прежде чем погружаться в детали, формализуем роль структурированной обработки исключений и посмотрим, чем она отличается от традиционных приемов обработки ошибок.
На заметку! Чтобы сделать примеры кода максимально ясными, мы не будем перехватывать абсолютно все исключения, которые может выдавать заданный метод из библиотеки базовых классов. Разумеется, в своих проектах производственного уровня вы должны широко использовать приемы, описанные в главе.
Роль обработки исключений .NET
До появления платформы .NET обработка ошибок в среде операционной системы Windows представляла собой запутанную смесь технологий. Многие программисты внедряли собственную логику обработки ошибок в контекст разрабатываемого приложения. Например, команда разработчиков могла определять набор числовых констант для представления известных условий возникновения ошибок и затем применять эти константы как возвращаемые значения методов. Взгляните на следующий фрагмент кода на языке С:
/* Типичный механизм перехвата ошибок в стиле С. */
#define E_FILENOTFOUND 1000
int UseFileSystem()
{
// Предполагается, что в этой функции происходит нечто
// такое, что приводит к возврату следующего значения.
return E_FILENOTFOUND;
}
void main()
{
int retVal = UseFileSystem();
if(retVal == E_FILENOTFOUND)
printf("Cannot find file..."); // H e удалось найти файл
}
Такой подход далек от идеала, учитывая тот факт, что константа E_FILENOTFOUND — всего лишь числовое значение, которое немногое говорит о том, каким образом решить возникшую проблему. В идеале желательно, чтобы название ошибки, описательное сообщение и другая полезная информация об условиях возникновения ошибки были помещены в единственный четко определенный пакет (что как раз и происходит при структурированной обработке исключений). В дополнение к специальным приемам, к которым прибегают разработчики, внутри API-интерфейса Windows определены сотни кодов ошибок, которые поступают в виде определений #define и HRESULT, а также очень многих вариаций простых булевских значений (bool, BOOL, VARIANT_BOOL и т.д.).