Создание и создание исключений

Исключения позволяют обозначить, что во время выполнения программы произошла ошибка. Объекты исключений, описывающие ошибку, создаются, а затем создаются с помощью инструкции throw или выражения. Далее среда выполнения ищет наиболее совместимый обработчик исключений.

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

  • Метод не способен выполнить свои заданные функции. Например, если значение параметра метода является недопустимым:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • На основе состояния объекта выполнен неправильный вызов объекта. В качестве примера можно привести попытку записи в файл, доступный только для чтения. В случаях, когда состояние объекта не допускает выполнения операции, вызывается экземпляр InvalidOperationException или объект на основе наследования этого класса. Ниже приведен пример метода, который вызывает объект InvalidOperationException:

    public class ProgramLog
    {
        FileStream logFile = null!;
        public void OpenLog(FileInfo fileName, FileMode mode) { }
    
        public void WriteLog()
        {
            if (!logFile.CanWrite)
            {
                throw new InvalidOperationException("Logfile cannot be read-only");
            }
            // Else write data to the log and return.
        }
    }
    
  • Когда аргумент метода вызывает исключение. В этом случае должно быть перехвачено исходное исключение и создан экземпляр ArgumentException. Исходное исключение должно передаваться конструктору ArgumentException в качестве параметра InnerException:

    static int GetValueFromArray(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    

    Примечание.

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

Исключения содержат свойство с именем StackTrace. Строка содержит имена методов в текущем стеке вызовов вместе с именем файла и номером строки, в которой было вызвано исключение для каждого метода. Объект StackTrace создается автоматически средой CLR из точки оператора throw, так что исключения должны вызываться из той точки, где должна начинаться трассировка стека.

Все исключения содержат свойство с именем Message. Эта строка должна содержать сообщение с объяснением причины исключения. Не следует помещать в текст сообщения сведения, важные с точки зрения безопасности. Помимо Message, ArgumentException содержит свойство с именем ParamName, которому должно присваиваться имя аргумента, ставшего причиной возникновения исключения. В методе задания свойства для ParamName следует установить значение value.

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

Действия, которые следует избегать при возникновении исключений

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

  • Не используйте исключения для изменения потока программы в рамках обычного выполнения. Используйте исключения для сообщения о состояниях ошибки и их обработки.
  • Исключения должны вызываться, а не возвращаться в качестве возвращаемого значения или параметра.
  • Не следует специально вызывать System.Exception, System.SystemException, System.NullReferenceException или System.IndexOutOfRangeException из собственного исходного кода.
  • Не рекомендуется создавать исключения, которые могут вызываться в режиме отладки, но не в режиме выпуска. Чтобы определить ошибки времени выполнения на этапе разработки, используйте Debug Assert.

Исключения в методах возврата задач

Методы, объявленные модификатором async , имеют некоторые особые аспекты, когда дело доходит до исключений. Исключения, создаваемые в методе async , хранятся в возвращаемой задаче и не возникают, пока, например, задача ожидается. Дополнительные сведения о хранимых исключениях см. в статье "Асинхронные исключения".

Рекомендуется проверить аргументы и вызвать все соответствующие исключения, например ArgumentException и ArgumentNullExceptionперед вводом асинхронных частей методов. То есть эти исключения проверки должны возникать синхронно перед началом работы. В следующем фрагменте кода показан пример, в котором, если возникают исключения, ArgumentException исключения будут синхронно возникать, в то время как они InvalidOperationException будут храниться в возвращаемой задаче.

// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
    if (slices is < 1 or > 4)
    {
        throw new ArgumentException(
            "You must specify between 1 and 4 slices of bread.",
            nameof(slices));
    }

    if (toastTime < 1)
    {
        throw new ArgumentException(
            "Toast time is too short.", nameof(toastTime));
    }

    return ToastBreadAsyncCore(slices, toastTime);

    // Local async function.
    // Within this function, any thrown exceptions are stored in the task.
    static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Putting a slice of bread in the toaster");
        }
        // Start toasting.
        await Task.Delay(time);

        if (time > 2_000)
        {
            throw new InvalidOperationException("The toaster is on fire!");
        }

        Console.WriteLine("Toast is ready!");

        return new Toast();
    }
}

Определение классов исключений

Программы могут вызывать предопределенный класс исключений в пространстве имен System (кроме указанных ранее случаев) или создавать собственные классы исключений путем наследования от Exception. Производные классы должны определять по крайней мере три конструктора: один конструктор без параметров, тот, который задает свойство сообщения, а также один, который задает Message как свойства, так и InnerException свойства. Например:

[Serializable]
public class InvalidDepartmentException : Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}

Добавляйте новые свойства в класс исключений только в том случае, если данные в них могут быть полезны для разрешения исключения. При добавлении новых свойств в производный класс исключений метод ToString() необходимо переопределить так, чтобы он возвращал добавленные сведения.

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

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

См. также