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


Создавать и выбрасывать исключения

Исключения используются для указания того, что при запуске программы произошла ошибка. Объекты исключений, описывающие ошибку, создаются, а затем выбрасываются с помощью инструкции 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#.

См. также