Обработка исключений (Руководство по программированию на C#)

Блок try используется программистами C# для разбиения на разделы кода, который может затрагиваться исключением. Связанные с ним блоки catch используются для обработки возможных исключений. Блок finally содержит код, выполняемый вне зависимости от того, вызывается ли исключение в блоке try, например для освобождения ресурсов, выделенных в блоке try. Блоку try требуется один или несколько связанных блоков catch или блок finally (либо и то, и другое).

В приведенных ниже примерах показаны операторы try-catch, try-finally и try-catch-finally.

try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
    // Only catch exceptions that you know how to handle.
    // Never catch base class System.Exception without
    // rethrowing it at the end of the catch block.
}
try
{
    // Code to try goes here.
}
finally
{
    // Code to execute after the try block goes here.
}
try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
}
finally
{
    // Code to execute after the try (and possibly catch) blocks
    // goes here.
}

Блок try без блока catch или блока finally вызовет ошибку компилятора.

Блоки catch

Блок catch может определять тип перехватываемого исключения. Спецификация типа называется фильтром исключений. Тип исключения должен быть производным от Exception. Как правило, не указывайте Exception в качестве фильтра исключений, если вы не знаете, как обрабатывать все исключения, которые могут быть брошены в блоке, или вы включилиthrowинструкцию в try конце catch блока.

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

Исключения следует перехватывать при выполнении указанных ниже условий.

  • Вы ясно понимаете возможные причины вызова исключения и можете выполнить восстановление, например, предложив пользователю ввести новое имя файла при перехвате объекта FileNotFoundException.
  • Вы можете создать и вызвать новое, более конкретное исключение.
    int GetInt(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    
  • Перед передачей исключения необходимо частично обработать исключение. В следующем примере блок catch используется для добавления записи в журнал ошибок перед повторным вызовом исключения.
    try
    {
        // Try to access a resource.
    }
    catch (UnauthorizedAccessException e)
    {
        // Call a custom error logging procedure.
        LogError(e);
        // Re-throw the error.
        throw;
    }
    

Вы также можете указывать фильтры исключений, чтобы добавить логическое выражение в предложение catch. Фильтры исключений указывают, что определенное предложение catch совпадает только в том случае, если это условие имеет значение true. В следующем примере оба предложения catch используют один и тот же класс исключений, но дополнительное условие проверка для создания другого сообщения об ошибке:

int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch (IndexOutOfRangeException e) when (index < 0) 
    {
        throw new ArgumentOutOfRangeException(
            "Parameter index cannot be negative.", e);
    }
    catch (IndexOutOfRangeException e)
    {
        throw new ArgumentOutOfRangeException(
            "Parameter index cannot be greater than the array size.", e);
    }
}

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

public class ExceptionFilter
{
    public static void Main()
    {
        try
        {
            string? s = null;
            Console.WriteLine(s.Length);
        }
        catch (Exception e) when (LogException(e))
        {
        }
        Console.WriteLine("Exception must have been handled");
    }

    private static bool LogException(Exception e)
    {
        Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
        Console.WriteLine($"\tMessage: {e.Message}");
        return false;
    }
}

Метод LogException всегда возвращает false, и ни одно предложение catch, использующее этот фильтр, не является соответствующим. Предложение catch может быть общим с использованием System.Exception, а последующие предложения могут обрабатывать более конкретные классы исключений.

Блоки "Finally"

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

Блок finally можно использовать для высвобождения ресурсов, например потоков данных, подключений к базам данных, графических дескрипторов, не ожидая финализации объектов сборщиком мусора во время выполнения.

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

FileStream? file = null;
FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
    file = fileinfo.OpenWrite();
    file.WriteByte(0xF);
}
finally
{
    // Check for null because OpenWrite might have failed.
    file?.Close();
}

Спецификация языка C#

Дополнительные сведения см. в разделах Исключения и Оператор try в Спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.

См. также