# Конструкция try..catch..finally ### Происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в консоли увидим сообщение об ошибке: ``` 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 /home/kei/RiderProjects/tryCatch/Program.cs:line 2 Process finished with exit code 134. ``` ### Следует использовать для обработки исключений конструкцию try...catch...finally. ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы"); Console.Read(); ``` Вывод: ``` Возникло исключение! Блок finally Конец программы ``` ### В этой конструкции обязателен блок try. При наличии блока catch мы можем опустить блок finally ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } ``` Вывод: ``` Возникло исключение! Process finished with exit code 0. ``` ### И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } finally { Console.WriteLine("Блок finally"); } ``` Вывод: ``` Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.
$(String[] args) in C:\Users\jissxdd\Desktop\labs\lab4_5 exceptions\lab4_5 exceptions\Program.cs:line 17 Блок finally Process finished with exit code -1,073,741,676. ``` # Обработка исключений и условные конструкции ### Метод Int32.TryParse() возвращает true, если преобразование можно осуществить, и false - если нельзя. При допустимости преобразования переменная x будет содержать введенное число. Так, не используя try...catch можно обработать возможную исключительную ситуацию. ``` Console.WriteLine("Введите число"); int x; string input = Console.ReadLine(); if (Int32.TryParse(input, out x)) { x *= x; Console.WriteLine("Квадрат числа: " + x); } else { Console.WriteLine("Некорректный ввод"); } Console.Read(); ``` Вывод: ``` Введите число 5a Некорректный ввод Process finished with exit code 0. ``` # Блок catch и фильтры исключений ### Обрабатывает любое исключение, которое возникло в блоке try. Выше уже был продемонстрирован пример подобного блока. ### Обработаем только исключения типа DivideByZeroException: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch(DivideByZeroException) { Console.WriteLine("Возникло исключение DivideByZeroException"); } ``` Вывод: ``` Возникло исключение DivideByZeroException Process finished with exit code 0. ``` ### В таком варианте обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора catch. А вся информация об исключении помещается в переменную данного типа. Например: ``` try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch(DivideByZeroException ex) { Console.WriteLine($"Возникло исключение {ex.Message}"); } ``` Вывод: ``` Возникло исключение Attempted to divide by zero. Process finished with exit code 0. ``` # Фильтры исключений ### В этом случае обработка исключения в блоке 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); } ``` Вывод: ``` Attempted to divide by zero. Process finished with exit code 0. ``` # Типы исключений. Класс Exception ### Базовым для всех типов исключений является тип Exception. Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении. ### Например, обработаем исключения типа 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\jissxdd\Desktop\labs\lab4_5 exceptions\lab4_5 exceptions\Program.cs:line 98 ``` ### При необходимости мы можем разграничить обработку различных типов исключений, включив дополнительные блоки catch: ``` 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(); ``` Вывод: ``` Index was outside the bounds of the array. Process finished with exit code 0. ``` ### Рассмотрим другую ситуацию.В данном случае в блоке try генерируется исключение типа InvalidCastException, однако соответствующего блока catch для обработки данного исключения нет. Поэтому программа аварийно завершит свое выполнение: ``` Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Int32'. at Program.
$(String[] args) in C:\Users\jissxdd\Desktop\labs\lab4_5 exceptions\lab4_5 exceptions\Program.cs:line 137 Process finished with exit code -532,462,766. ``` Вывод: ``` Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Int32'. at Program.
$(String[] args) in C:\Users\jissxdd\Desktop\labs\lab4_5 exceptions\lab4_5 exceptions\Program.cs:line 137 Process finished with exit code -532,462,766. ``` ### Определять для всех типов исключений блоки 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(); ``` Вывод: ``` Исключение: Unable to cast object of type 'System.String' to type 'System.Int32'. Process finished with exit code 0. ``` # Создание классов исключений ### Если нас не устраивают встроенные типы исключений, то мы можем создать свои типы. Базовым классом для всех исключений является класс Exception, соответственно для создания своих типов мы можем унаследовать данный класс. ### Допустим, у нас в программе будет ограничение по возрасту: ``` 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 регистрация запрещена Process finished with exit code 0. ``` ### Иногда удобнее использовать свои классы исключений. Например, в какой-то ситуации мы хотим обработать определенным образом только те исключения, которые относятся к классу (Person). Для этих целей мы можем сделать специальный класс PersonException. ### По сути класс кроме пустого конструктора ничего не имеет, и то в конструкторе мы просто обращаемся к конструктору базового класса Exception, передавая в него строку message. Но теперь мы можем изменить класс Person, чтобы он выбрасывал исключение именно этого типа и соответственно в основной программе обрабатывать это исключение: ``` class PersonException : Exception { public PersonException(string message) : base(message) { } } ``` ``` try { Person p = new Person { Name = "Tom", Age = 17 }; } catch (PersonException ex) { Console.WriteLine("Ошибка: " + ex.Message); } Console.Read(); class Person { private int age; public int Age { get { return age; } set { if (value < 18) throw new PersonException("Лицам до 18 регистрация запрещена"); else age = value; } } } ``` Вывод: ``` Ошибка: Лицам до 18 регистрация запрещена Process finished with exit code 0. ``` ### Каждый тип исключений может определять какие-то свои свойства. Например, в данном случае мы можем определить в классе свойство для хранения устанавливаемого значения. ### В конструкторе класса мы устанавливаем это свойство и при обработке исключения мы его можем получить: ``` class PersonException : ArgumentException { public int Value { get;} public PersonException(string message, int val) : base(message) { Value = val; } } ``` ``` try { Person p = new Person { Name = "Tom", Age = 13 }; } catch (PersonException ex) { Console.WriteLine($"Ошибка: {ex.Message}"); Console.WriteLine($"Некорректное значение: {ex.Value}"); } Console.Read(); class Person { public string Name { get; set; } private int age; public int Age { get { return age; } set { if (value < 18) throw new PersonException( "Лицам до 18 регистрация запрещена", value); else age = value; } } } ``` Вывод: ``` Ошибка: Лицам до 18 регистрация запрещена Некорректное значение: 13 Process finished with exit code 0. ``` # Поиск блока catch при обработке исключений ### Если код, который вызывает исключение, не размещен в блоке try или помещен в конструкцию try..catch, которая не содержит соответствующего блока 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 ### Например, в нашей программе происходит ввод строки, и мы хотим, чтобы, если длина строки будет больше 6 символов, возникало исключение: ``` 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 символов Process finished with exit code 0. ``` ### Существует также и другая форма использования оператора throw, когда после данного оператора не указывается объект исключения. В подобном виде оператор throw может использоваться только в блоке catch: ``` try { try { Console.Write("Введите строку: "); string message = Console.ReadLine(); if (message.Length > 6) { throw new Exception( "Длина строки больше 6 символов"); } } catch { Console.WriteLine("Возникло исключение"); throw; } } catch (Exception ex) { Console.WriteLine(ex.Message); } ``` Вывод: ``` Введите строку: 1234567 Возникло исключение Длина строки больше 6 символов Process finished with exit code 0. ``` # NULL ### Начиная с версии C# 7.0 можно использовать оператор is с шаблоном типа как для проверки экземпляра типа, допускающего значение NULL, для 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 ``` ### В следующем примере используется свойство HasValue, чтобы проверить, содержит ли переменная значение, перед его отображением: ``` int? b = 10; if (b.HasValue) { Console.WriteLine($"b is {b.Value}"); } else { Console.WriteLine("b does not have a value"); } ``` Вывод: ``` b is 10 ``` ### Можно также сравнить переменную типа значения, допускающего значение NULL, с null вместо использования свойства HasValue, как показано в следующем примере: ``` int? c = 7; if (c != null) { Console.WriteLine($"c is {c.Value}"); } else { Console.WriteLine("c does not have a value"); } ``` Вывод: ``` c is 7 ``` ### Если необходимо присвоить значение типа, допускающего значение NULL, переменной типа значения, не допускающего значения NULL, может потребоваться указать значение, назначаемое вместо null. Для этого используйте оператор объединения со значением NULL ??: ``` 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 ``` Вывод: ``` b is 28 d is -1 ```