Criar e lançar exceções

As exceções são usadas para indicar que ocorreu um erro durante a execução do programa. Objetos de exceção que descrevem um erro são criados e, em seguida, lançados com a instrução ou expressão throw. Então, o runtime procura o manipulador de exceção 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 tem um valor inválido:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • É feita uma chamada inadequada a um objeto, com base no estado do objeto. Um exemplo pode ser a tentativa de gravar em um arquivo somente leitura. Em casos em que um estado do objeto não permita 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 gera um objeto 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.
        }
    }
    
  • Quando um argumento para um método causa uma exceção. Nesse caso, a exceção original deve ser capturada e uma instância de ArgumentException deve ser criada. A exceção original deve ser passada para o construtor do ArgumentException como o parâmetro 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);
        }
    }
    

    Observação

    O exemplo anterior mostra como usar a propriedade InnerException. Ele foi simplificado intencionalmente. 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 gera 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, junto com o nome de arquivo e o número de linha em que a exceção foi lançada para cada método. Um objeto StackTrace é criado automaticamente pelo CLR (Common Language Runtime) no ponto da instrução throw, de modo que as exceções devem ser lançadas do ponto em que 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 que são sensíveis à segurança não devem ser colocadas no texto da mensagem. Além Message, ArgumentException contém uma propriedade chamada ParamName que deve ser definida como o nome do argumento que causou a exceção a ser lançada. Em um setter de propriedade, ParamName deve ser definido como value.

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

O que deve ser evitado na geração de 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 normal. Use exceções para relatar e lidar com condições de erro.
  • As exceções não devem ser retornadas como um valor retornado ou um parâmetro em vez de serem lançadas.
  • Não lance System.Exception, System.SystemException, System.NullReferenceException ou System.IndexOutOfRangeException intencionalmente de seu próprio código-fonte.
  • Não crie exceções que podem ser lançadas no modo de depuração, mas não no modo de versão. Em vez disso, use o Debug Assert para identificar erros em tempo de execução durante a fase de desenvolvimento.

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

Os métodos declarados com o modificador async têm algumas considerações especiais quando se trata de exceções. As exceções geradas em um método async 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, confira exceções assíncronas.

Recomendamos que você valide argumentos e gere 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 snippet de código a seguir mostra um exemplo em que, se as exceções fossem geradas, as exceções ArgumentException surgiriam de forma síncrona, enquanto as InvalidOperationException seriam armazenadas 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 namespace System (exceto quando observado 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 de mensagem e um que define as propriedades Message 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 que elas fornecem forem úteis para resolver a exceção. Se forem adicionadas novas propriedades à classe de exceção derivada, ToString() deverá ser substituído para retornar as informações adicionadas.

Especificação da linguagem C#

Para obter mais informações, veja Exceções e A declaração throw na Especificação da Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Confira também