**Конспект лекции "Исключения"** Конструкция try..catch..finally, простыми словами - это замена if и else. Она подходит для записи больших условий при том условии, что код не будет выглядеть большим. ``` try { } catch { } finally { } ``` **1)** Код выглядит следующим образом: ``` int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); Console.WriteLine("Конец программы"); Console.Read(); ``` Код выводит следующую ошибку из-за неверного математического вычисления: ``` Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.
$(String[] args) in C:\Users\шоумен\RiderProjects\lab8\lab8\Program.cs:line 2 Process finished with exit code -1,073,741,676. ``` Код выглядит следующим образом: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы"); Console.Read(); ``` Из-за неверного деления программа выведет следующее: ``` Возникло исключение! Блок finally Конец программы ``` Код выглядит следующим образом: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } ``` Результат кода таков: ``` Возникло исключение! ``` Код выглядит следующим образом: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } finally { Console.WriteLine("Блок finally"); } ``` Результат кода таков: ``` Блок finally ``` Сейчас данная задача написана с условием, но ее можно написать и по другому для более приятного восприятия кода. ``` Console.WriteLine("Введите число"); int x; string input = Console.ReadLine(); if (Int32.TryParse(input, out x)) { x *= x; Console.WriteLine("Квадрат числа: " + x); } else { Console.WriteLine("Некорректный ввод"); } Console.Read(); ``` По другому код можно записать таким образом: ``` Console.WriteLine("Введите число"); int x = Int32.Parse(Console.ReadLine()); x *= x; Console.WriteLine("Квадрат числа: " + x); Console.Read(); ``` У блока catch есть 2 формы. Одна из них выглядит таким образом: ``` catch { // выполняемые инструкции } ``` Оно обрабатывает любое исключение, которое появляется в блоке try. Форая форма выглядит немного иначе: ``` catch (тип_исключения) { // выполняемые инструкции } ``` Это блок обрабатывает только то исключение, которое соответсвтует типу, указанному в скобках. Так же если в блоке try будет другое исключение то оно не будет обработано. Пример такой программы: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch(DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } ``` Так же есть еще один вариант, где обрабатывается только то исключение, которое соответствует типу указанных в скобках: ``` catch (тип_исключения имя_переменной) { // выполняемые инструкции } ``` Пример такой программы: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch(DivideByZeroException ex) { Console.WriteLine($"Возникло исключение {ex.Message}"); } ``` **Фильтры исключений** Фильтры, в которых после блока идет вырвжение с условием в свкобках. ``` catch when(условие) { } ``` Пример такой программы, которая выполняет исключение, если условие в выражении истинно: ``` int x = 1; int y = 0; try { int result = x / y; } catch(DivideByZeroException) when (y==0 && x == 0) { Console.WriteLine("y не должен быть равен 0"); } catch(DivideByZeroException ex) { Console.WriteLine(ex.Message); } ``` **Типы исключений. Класс Exception** Базовый тип исключения - это тип **Exception**. С помощью этого типа можно получить информацию об исключении. InnerException: хранит информацию об исключении, которое послужило причиной текущего исключения Message: хранит сообщение об исключении, текст ошибки Source: хранит имя объекта или сборки, которое вызвало исключение StackTrace: возвращает строковое представление стека вызывов, которые привели к возникновению исключения TargetSite: возвращает метод, в котором и было вызвано исключение **Пример обработки типа Exception.** ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch (Exception ex) { Console.WriteLine($"Исключение: {ex.Message}"); Console.WriteLine($"Метод: {ex.TargetSite}"); Console.WriteLine($"Трассировка стека: {ex.StackTrace}"); } Console.Read(); ``` Код выводит такой текст: ``` Исключение: Attempted to divide by zero. Метод: Void
$(System.String[]) Трассировка стека: at Program.
$(String[] args) in C:\Users\шоумен\RiderProjects\lab8\lab8\Program.cs:line 4 ``` Из-за того, что тип Exception является базовым типом для всех исключений, то выражение catch (Exception ex) будет обрабатывать все исключения, которые могут возникнуть: Так же есть специализированные типы исключений, которые нужны для обработки определнных типов исключений: DivideByZeroException: представляет исключение, которое генерируется при делении на ноль ArgumentOutOfRangeException: генерируется, если значение аргумента находится вне диапазона допустимых значений ArgumentException: генерируется, если в метод для параметра передается некорректное значение IndexOutOfRangeException: генерируется, если индекс элемента массива или коллекции находится вне диапазона допустимых значений InvalidCastException: генерируется при попытке произвести недопустимые преобразования типов NullReferenceException: генерируется при попытке обращения к объекту, который равен null (то есть по сути неопределен) Еще мы можем выключить дополительные блоки catch, это как else if. Пример кода: ``` try { int[] numbers = new int[4]; numbers[7] = 9; // IndexOutOfRangeException int x = 5; int y = x / 0; // DivideByZeroException Console.WriteLine($"Результат: {y}"); } catch (DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } catch (IndexOutOfRangeException ex) { Console.WriteLine(ex.Message); } Console.Read(); ``` В данном случае блоки catch обрабатывают исключения типов IndexOutOfRangeException и DivideByZeroException. Когда в блоке try возникнет исключение, то CLR будет искать нужный блок catch для обработки исключения. Так, в данном случае на строке ``` numbers[7] = 9; ``` происходит обращение к 7-му элементу массива. Однако поскольку в массиве только 4 элемента, то мы получим исключение типа IndexOutOfRangeException. CLR найдет блок catch, который обрабатывает данное исключение, и передаст ему управление. Есть другая ситуация: ``` try { object obj = "you"; int num = (int)obj; // InvalidCastException Console.WriteLine($"Результат: {num}"); } catch (DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } catch (IndexOutOfRangeException) { Console.WriteLine("Возникло исключение IndexOutOfRangeException"); } Console.Read(); ``` В данном случае в блоке try генерируется исключение типа InvalidCastException, но такого блока catch не существует, поэтому программа принудительно завершится. Мы также можем определить для InvalidCastException свой блок catch, однако суть в том, что теоретически в коде могут быть сгенерированы сами различные типы исключений. А определять для всех типов исключений блоки catch, если обработка исключений однотипна, не имеет смысла. И в этом случае мы можем определить блок catch для базового типа Exception: ``` try { object obj = "you"; int num = (int)obj; // InvalidCastException Console.WriteLine($"Результат: {num}"); } catch (DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } catch (IndexOutOfRangeException) { Console.WriteLine("Возникло исключение IndexOutOfRangeException"); } catch (Exception ex) { Console.WriteLine($"Исключение: {ex.Message}"); } Console.Read(); ``` **Создание классов исключений** **Ограничение по возрасту** ``` try { Person p = new Person { Name = "Tom", Age = 17 }; } catch (Exception ex) { Console.WriteLine($"Ошибка: {ex.Message}"); } Console.Read(); class Person { private int age; public string Name { get; set; } public int Age { get { return age; } set { if (value < 18) { throw new Exception("Лицам до 18 регистрация запрещена"); } else { age = value; } } } } ``` ``` Ошибка: Лицам до 18 регистрация запрещена ``` **Поиск блока catch при обработке исключений** ``` try { TestClass.Method1(); } catch (DivideByZeroException ex) { Console.WriteLine($"Catch в Main : {ex.Message}"); } finally { Console.WriteLine("Блок finally в Main"); } Console.WriteLine("Конец метода Main"); Console.Read(); 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"); } } ``` ``` Блок finally в Method2 Блок finally в Method1 Catch в Main : Attempted to divide by zero. Блок finally в Main Конец метода Main ``` **Генерация исключения и оператор throw** ``` try { Console.Write("Введите строку: "); string message = Console.ReadLine(); if (message.Length > 6) { throw new Exception( "Длина строки больше 6 символов"); } } catch (Exception e) { Console.WriteLine($"Ошибка: {e.Message}"); } Console.Read(); ``` ``` Введите строку: 1234567 Ошибка: Длина строки больше 6 символов ``` **Проверка экземпляра типа значения, допускающего значение NULL** ``` int? a = 42; if (a is int valueOfA) { Console.WriteLine($"a is {valueOfA}"); } else { Console.WriteLine("a does not have a value"); } ``` ``` a is 42 ``` ``` int? a = 28; int b = a ?? -1; Console.WriteLine($"b is {b}"); // output: b is 28 int? c = null; int d = c ?? -1; Console.WriteLine($"d is {d}"); // output: d is -1 ``` **Операторы с нулификацией** ``` int? a = 10; int? b = null; int? c = 10; a++; // a is 11 a = a * c; // a is 110 a = a + b; // a is null ``` ``` int? a = 10; Console.WriteLine($"{a} >= null is {a >= null}"); Console.WriteLine($"{a} < null is {a < null}"); Console.WriteLine($"{a} == null is {a == null}"); // Output: // 10 >= null is False // 10 < null is False // 10 == null is False int? b = null; int? c = null; Console.WriteLine($"null >= null is {b >= c}"); Console.WriteLine($"null == null is {b == c}"); // Output: // null >= null is False // null == null is True ``` **Упаковка-преобразование и распаковка-преобразование** Если HasValue возвращает false, создается пустая ссылка. Если HasValue возвращает true, упаковывается соответствующее значение базового типа T, а не экземпляр Nullable. ``` int a = 41; object aBoxed = a; int? aNullable = (int?)aBoxed; Console.WriteLine($"Value of aNullable: {aNullable}"); object aNullableBoxed = aNullable; if (aNullableBoxed is int valueOfA) { Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}"); } // Output: // Value of aNullable: 41 // aNullableBoxed is boxed int: 41 ``` **Оператор ??** ``` object x = null; object y = x ?? 100; // равно 100, так как x равен null object z = 200; object t = z ?? 44; // равно 200, так как z не равен null ``` **Оператор условного null** ``` class User { public Phone Phone { get; set; } } class Phone { public Company Company { get; set; } } class Company { public string Name { get; set; } } ``` Объект User содержит ссылку на объект Phone, а объект Phone содержит ссылку на объект Company, поэтому теоретически мы можем получить из объекта User название компании, например, так: ``` User user = new User(); Console.WriteLine(user.Phone.Company.Name); ```