Criar e lançar exceções

As exceções são usadas para indicar que ocorreu um erro durante a execução do programa. Os objetos de exceção que descrevem um erro são criados e, em seguida, lançados com a throw instrução ou expressão. Em seguida, o tempo de execução procura o manipulador de exceções mais compatível.

Os programadores devem lançar exceções quando uma ou mais das seguintes condições forem verdadeiras:

  • O método não pode concluir sua funcionalidade definida. Por exemplo, se um parâmetro para um método tiver um valor inválido:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Uma chamada inadequada para um objeto é feita, com base no estado do objeto. Um exemplo pode ser tentar gravar em um arquivo somente leitura. Nos casos em que um estado de objeto não permite uma operação, lance uma instância de InvalidOperationException ou um objeto com base em uma derivação dessa classe. O código a seguir é um exemplo de um método que lança um InvalidOperationException objeto:

    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.
        }
    }
    
  • Quando um argumento para um método causa uma exceção. Neste caso, a exceção original deve ser capturada e uma ArgumentException instância deve ser criada. A exceção original deve ser passada para o construtor do ArgumentException parâmetro as 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);
        }
    }
    

    Nota

    O exemplo anterior mostra como usar a InnerException propriedade. É intencionalmente simplificado. Na prática, você deve verificar se um índice está no intervalo antes de usá-lo. Você pode usar essa técnica de encapsular uma exceção quando um membro de um parâmetro lança uma exceção que você não poderia prever antes de chamar o membro.

As exceções contêm uma propriedade chamada StackTrace. Essa cadeia de caracteres contém o nome dos métodos na pilha de chamadas atual, juntamente com o nome do arquivo e o número da linha onde a exceção foi lançada para cada método. Um StackTrace objeto é criado automaticamente pelo Common Language Runtime (CLR) a partir do ponto da instrução, de throw modo que as exceções devem ser lançadas a partir do ponto onde o rastreamento de pilha deve começar.

Todas as exceções contêm uma propriedade chamada Message. Essa cadeia de caracteres deve ser definida para explicar o motivo da exceção. As informações sensíveis à segurança não devem ser colocadas no texto da mensagem. Além de Message, ArgumentException contém uma propriedade chamada ParamName que deve ser definida como o nome do argumento que causou o lançamento da exceção. Em um setter de propriedade, ParamName deve ser definido como value.

Métodos públicos e protegidos lançam exceções sempre que não conseguem concluir as funções pretendidas. A classe de exceção lançada é a exceção mais específica disponível que se encaixa nas condições de erro. Essas exceções devem ser documentadas como parte da funcionalidade de classe, e as classes derivadas ou atualizações para a classe original devem manter o mesmo comportamento para compatibilidade com versões anteriores.

Coisas a evitar ao lançar exceções

A lista a seguir identifica as práticas a serem evitadas ao lançar exceções:

  • Não use exceções para alterar o fluxo de um programa como parte da execução comum. Use exceções para relatar e lidar com condições de erro.
  • As exceções não devem ser retornadas como um valor ou parâmetro de retorno em vez de serem lançadas.
  • Não lance System.Exception, System.SystemException, System.NullReferenceException, ou System.IndexOutOfRangeException intencionalmente do seu próprio código-fonte.
  • Não crie exceções que possam ser lançadas no modo de depuração, mas não no modo de lançamento. Para identificar erros em tempo de execução durante a fase de desenvolvimento, use Debug Assert.

Exceções em métodos de retorno de tarefas

Os métodos declarados com o async modificador têm algumas considerações especiais quando se trata de exceções. As exceções lançadas em um async método são armazenadas na tarefa retornada e não surgem até que, por exemplo, a tarefa seja aguardada. Para obter mais informações sobre exceções armazenadas, consulte Exceções assíncronas.

Recomendamos que você valide argumentos e lance quaisquer exceções correspondentes, como ArgumentException e ArgumentNullException, antes de inserir as partes assíncronas de seus métodos. Ou seja, essas exceções de validação devem surgir de forma síncrona antes do início do trabalho. O trecho de código a seguir mostra um exemplo em que, se as exceções forem lançadas, as ArgumentException exceções surgirão de forma síncrona, enquanto o InvalidOperationException será armazenado na tarefa retornada.

// 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();
    }
}

Definir classes de exceção

Os programas podem lançar uma classe de exceção predefinida no System namespace (exceto onde indicado anteriormente) ou criar suas próprias classes de exceção derivando de Exception. As classes derivadas devem definir pelo menos três construtores: um construtor sem parâmetros, um que define a propriedade message e um que define as Message propriedades e InnerException . Por exemplo:

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

Adicione novas propriedades à classe de exceção quando os dados fornecidos forem úteis para resolver a exceção. Se novas propriedades forem adicionadas à classe de exceção derivada, ToString() deverão ser substituídas para retornar as informações adicionadas.

Especificação da linguagem C#

Para obter mais informações, consulte Exceptions and The throw statement na C# Language Specification. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso do C#.

Consulte também