Поделиться через


Использование исключений

В C# ошибки в программе в среде выполнения передаются через программу с помощью механизма, который называется исключениями. Исключения вызываются кодом, который встречает ошибку, и перехватываются кодом, который может ее исправить. Исключения могут вызываться средой выполнения .NET или кодом в программе. Вызванное исключение передается вверх по стеку вызовов, пока не будет найден соответствующий оператор catch. Не перехваченные исключения обрабатываются универсальным обработчиком исключений, предоставляемым системой, которая отображает диалоговое окно.

Исключения представляются классами, производными от Exception. Этот класс определяет тип исключения и содержит свойства с подробными сведениями об исключении. При вызове исключения создается экземпляр производного класса, а также могут настраиваться свойства исключения. После этого с помощью ключевого слова throw вызывается объект. Например:

class CustomException : Exception
{
    public CustomException(string message)
    {
    }
}
private static void TestThrow()
{
    throw new CustomException("Custom exception in TestThrow()");
}

После выдачи исключения среда выполнения проверяет, входит ли текущий оператор в блок try. Если да, она проверяет, может ли какой-либо из блоков catch, связанных с блоком try, перехватить исключение. Блоки Catch обычно задают типы исключений; если тип блока catch совпадает с типом или базовым классом исключения, блок catch может обработать этот метод. Например:

try
{
    TestThrow();
}
catch (CustomException ex)
{
    System.Console.WriteLine(ex.ToString());
}

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

Оператор try может содержать не один блок catch. Выполняется первый оператор catch, который может обработать исключение; все последующие операторы catch игнорируются, даже если они совместимы. Порядок перехвата блокируется от наиболее конкретных (или наиболее производных) до наименее конкретных. Например:

using System;
using System.IO;

namespace Exceptions
{
    public class CatchOrder
    {
        public static void Main()
        {
            try
            {
                using (var sw = new StreamWriter("./test.txt"))
                {
                    sw.WriteLine("Hello");
                }
            }
            // Put the more specific exceptions first.
            catch (DirectoryNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine(ex);
            }
            // Put the least specific exception last.
            catch (IOException ex)
            {
                Console.WriteLine(ex);
            }
            Console.WriteLine("Done");
        }
    }
}

Прежде чем выполнять блок catch, среда выполнения проверяет наличие блоков finally. Блоки Finally позволяют программисту удалить любое неоднозначное состояние, которое может остаться после прерванного блока try, а также освободить любые внешние ресурсы (включая обработчики графики, соединители баз данных или файловые потоки), не дожидаясь, пока сборщик мусора в среде выполнения завершит эти объекты. Например:

static void TestFinally()
{
    FileStream? file = null;
    //Change the path to something that works on your machine.
    FileInfo fileInfo = new System.IO.FileInfo("./file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
        file?.Close();
    }

    try
    {
        file = fileInfo.OpenWrite();
        Console.WriteLine("OpenWrite() succeeded");
    }
    catch (IOException)
    {
        Console.WriteLine("OpenWrite() failed");
    }
}

Если WriteByte() вызывает исключение, то код во втором блоке try, который пытается повторно открыть файл, завершится ошибкой, если file.Close() не вызывается, а файл остается заблокированным. Поскольку блоки finally выполняются, даже если выдается исключение, блок finally в предыдущем примере обеспечивает правильное закрытие файла и помогает избежать ошибки.

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

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