Обработка исключений C#

C Sharp > Обработка исключений C#
06.02.2018 16:07:39



Статья:

Хорошо спроектированное приложение обрабатывает исключения и ошибки, чтобы предотвратить сбои приложения.

В следующем примере используется блок try/catch, который проверяет подключение и вызывает исключение, если подключение не закрыто.

try
{
    conn.Close();
}
catch (InvalidOperationException ex)
{
    Console.WriteLine(ex.GetType().FullName);
    Console.WriteLine(ex.Message);
}
  • Используйте блоки try/catch выделив с их помощью тот код, который потенциально может вызвать исключение, и используйте блок finally для очистки ресурсов, если это необходимо. При таком способе оператор try создает исключение, оператор catchобрабатывает исключение, а оператор finally закрывает или освобождает ресурсы.

  • В блоках catch следует всегда упорядочивать исключения от более конкретных к более общим. При таком методе определенное исключение обрабатывается до его передачи в блок catch более общего исключения.

В C# и C++ при создании собственных классов исключений можно использовать по меньшей мере три общих конструктора: конструктор по умолчанию, конструктор, принимающий строковое сообщение, и конструктор, принимающий строковое сообщение и внутреннее исключение. Пример

using System;

public class EmployeeListNotFoundException: Exception
{
    public EmployeeListNotFoundException()
    {
    }

    public EmployeeListNotFoundException(string message)
        : base(message)
    {
    }

    public EmployeeListNotFoundException(string message, Exception inner)
        : base(message, inner)
    {
    }
}

 

  1. Exception(), использующий значения по умолчанию.

  2. Exception(String), принимающий строковое сообщение.

  3. Exception(String,?Exception), принимающий строковое сообщение и внутреннее исключение.

  • При создании пользовательских исключений необходимо обеспечить доступность метаданных исключений для удаленно исполняемого кода, включая и те исключения, которые возникают между доменами приложений. Например, предположим, что домен приложения А создает домен приложения В, который выполняет код, порождающий исключение. Чтобы домен приложения A правильно перехватил и обработал это исключение, он должен иметь возможность найти сборку, содержащую исключение, вызванное доменом приложения B. Если домен приложения B вызывает исключение, содержащееся в его базе приложения, но не в базе домена приложения A, то домен приложения A не сможет найти исключение и среда CLR вызовет исключениеFileNotFoundException. Чтобы избежать такой ситуации, можно развернуть сборку, содержащую сведения об исключении, двумя способами:

    • Поместите эту сборку в общую базу приложения, совместно используемую обоими доменами приложений.

      -или-

    • Если у этих доменов нет общей базы приложения, то подпишите сборку, содержащую сведения об исключении, строгим именем и разверните ее в глобальном кэше сборок.

  • В каждое исключение включайте локализованную строку описания. Сообщение об ошибке, показываемое пользователю, извлекается из строки описания созданного исключения, а не из класса исключения.

  • Используйте методы построителя исключений. Обычно класс генерирует одно и то же исключение из различных мест своей реализации. Чтобы избежать повторения кода, используйте вспомогательные методы, создающие исключение и затем возвращающие его. Например:

  • class FileReader
    {
        private string fileName;
    
        public FileReader(string path)
        {
            fileName = path;
        }
    
        public byte[] Read(int bytes)
        {
            byte[] results = FileUtils.ReadFromFile(fileName, bytes);
            if (results == null)
            {
                throw NewFileIOException();
            }
            return results;
        }
    
        FileReaderException NewFileIOException()
        {
            string description = "My NewFileIOException Description";
    
            return new FileReaderException(description);
        }
    }

Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями. Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого в C# предназначена конструкция try...catch...finally. При возникновении исключения среда CLR ищет блок catch, который может обработать данное исключение. Если такого блока не найдено, то пользователю отображается сообщение о необработанном исключении, а дальнейшее выполнение программы останавливается. И чтобы подобной остановки не произошло, и надо использовать блок try..catch. Например:

static void Main(string[] args)
{
    int[] a = new int[4];
    try
    {
        a[5] = 4; // тут возникнет исключение, так как у нас в массиве только 4 элемента
        Console.WriteLine("Завершение блока try");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Ошибка: " + ex.Message);
    }
    finally
    {
        Console.WriteLine("Блок finally");
    }
    Console.ReadLine();
}

При использовании блока try...catch..finally вначале выполняются все инструкции между операторами try и catch. Если между этими операторами вдруг возникает исключение, то обычный порядок выполнения останавливается и переходит к инструкции сatch. В данном случае у нас явно возникнет исключение в блоке try, так как мы пытаемся присвоить значение шестому элементу массива в то время, как в массиве всего 4 элемента. И дойдя до строки a[5] = 4;, выполнение программы остановится и перейдет к блоку catch

Инструкция catch имеет следующий синтаксис: catch (тип_исключения имя_переменной). В нашем случае объявляется переменная ex, которая имеет тип Exception. Но если возникшее исключение не является исключением типа, указанного в инструкции сatch, то оно не обрабатывается, а программа просто зависает или выбрасывает сообщение об ошибке.

Однако так как тип Exception является базовым классом для всех исключений, то выражение catch (Exception ex) будет обрабатывать практически все исключения. Вся обработка исключения в нашем случае сводится к выводу на консоль сообщении об исключении, которое в свойстве message класса Exception.

Далее в любом случае выполняется блок finally. Однако этот блок необязательный, и его можно при обработке исключений опускать. Если же в ходе программы исключений не возникнет, то программа не будет выполнять блок catch, сразу перейдет к блоку finally, если он имеется.

При необходимости мы можем разграничить обработку различных типов исключений, включив дополнительные блоки catch:

static void Main(string[] args)
{
    try
    {
 
    }
    catch (FileNotFoundException e)
    {
        // Обработка исключения, возникшего при отсутствии файла
    }
    catch (IOException e)
    {
        // Обработка исключений ввода-вывода
    }
    Console.ReadLine();
}

Оператор throw

Чтобы сообщить о выполнении исключительных ситуаций в программе, можно использовать оператор throw. То есть с помощью этого оператора мы сами можем создать исключение и вызвать его в процессе выполнения. Например, в нашей программе происходит ввод строки, и мы хотим, чтобы, если длина строки будет больше 6 символов, возникало исключение:

static void Main(string[] args)
{
    try
    {
        string message = Console.ReadLine();
        if (message.Length > 6)
        {
            throw new Exception("Длина строки больше 6 символов");
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Ошибка: " + e.Message);
    }
    Console.ReadLine();
}

Фильтры исключений

В C# 6.0 (Visual Studio 2015) была добавлена такая функциональность, как фильтры исключений. Они позволяют обрабатывать исключения в зависимости от определенных условий:

int x = 1;
int y = 0;
 
try
{
    int result = x / y;
}
catch(Exception ex) when (y==0)
{
    Console.WriteLine("y не должен быть равен 0");
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

В данном случае будет выброшено исключение, так как y=0. Здесь два блока catch, но поскольку для первого блока указано условие с помощью ключевого слова when, то сработает первый блок catch. Если бы y не было бы равно 0, то сработал бы второй блок catch.