Без опису

ebakhtin 0ab2973478 exceptions1 6 місяців тому
Program.cs 0ab2973478 exceptions1 6 місяців тому
lab4_5_exceptions.csproj 0ab2973478 exceptions1 6 місяців тому
lab4_5_exceptions.sln 0ab2973478 exceptions1 6 місяців тому
readme.md 0ab2973478 exceptions1 6 місяців тому

readme.md

Конструкция 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.<Main>$(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.<Main>$(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 <Main>$(System.String[])
Трассировка стека:    at Program.<Main>$(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.<Main>$(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.<Main>$(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