Instruções para manipulação de exceções – throw, try-catch, try-finally e try-catch-finally

Use as instruções throw e try para trabalhar com exceções. Use a instruçãothrow para gerar uma exceção. Use a instruçãotry para capturar e tratar exceções que podem ocorrer durante a execução de um bloco de código.

A instrução throw

A instrução throw lança uma exceção:

if (shapeAmount <= 0)
{
    throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}

Em uma instrução throw e;, o resultado da expressão e deve ser implicitamente conversível para System.Exception.

Você pode usar as classes de exceção internas, por exemplo, ArgumentOutOfRangeException ou InvalidOperationException. O .NET também fornece os métodos auxiliares para gerar exceções em determinadas condições: ArgumentNullException.ThrowIfNull e ArgumentException.ThrowIfNullOrEmpty. Você também pode definir suas classes de exceção derivadas de System.Exception. Para obter mais informações, consulte Criando e lançando exceções.

Dentro de um bloco catch, você pode usar uma instrução throw; para gerar novamente a exceção que é tratada pelo bloco catch:

try
{
    ProcessShapes(shapeAmount);
}
catch (Exception e)
{
    LogError(e, "Shape processing failed.");
    throw;
}

Observação

throw; preserva o rastreamento de pilha original da exceção, que é armazenado na propriedade Exception.StackTrace. Ao contrário disso, throw e; atualiza a propriedade StackTrace de e.

Quando uma exceção é lançada, o CLR (Common Language Runtime) procura o bloco catch que trata essa exceção. Se o método executado no momento não contiver um bloco catch, o CLR procurará no método que chamou o método atual e assim por diante para cima na pilha de chamadas. Se nenhum bloco catch for encontrado, o CLR encerrará o thread em execução. Para obter mais informações, consulte a seção Como exceções são tratadas da Especificação da linguagem C#.

A expressão throw

Você também pode usar throw como expressão. Isso pode ser conveniente em vários casos, que incluem:

  • o operador condicional. O seguinte exemplo usa uma expressão throw para gerar um ArgumentException quando a matriz passada args está vazia:

    string first = args.Length >= 1 
        ? args[0]
        : throw new ArgumentException("Please supply at least one argument.");
    
  • o operador de união nula. O seguinte exemplo usa uma expressão throw para gerar um ArgumentNullException quando a cadeia de caracteres a ser atribuída a uma propriedade é null:

    public string Name
    {
        get => name;
        set => name = value ??
            throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
    }
    
  • um método ou lambda com corpo de expressão. O seguinte exemplo usa uma expressão throw para gerar um InvalidCastException para indicar que não há suporte para uma conversão em um valor DateTime:

    DateTime ToDateTime(IFormatProvider provider) =>
             throw new InvalidCastException("Conversion to a DateTime is not supported.");
    

A instrução try

Você pode usar a instrução try em qualquer uma das seguintes formas: try-catch - para lidar com exceções que podem ocorrer durante a execução do código dentro de um bloco try, try-finally - para especificar o código que é executado quando o controle sai do bloco try e try-catch-finally - como uma combinação dos dois formatos anteriores.

A instrução try-catch

Use a instruçãotry-catch para tratar exceções que podem ocorrer durante a execução de um bloco de código. Coloque o código em que uma exceção pode ocorrer dentro de um bloco try. Use uma cláusula catch para especificar o tipo base das exceções que você deseja manipular no bloco catch correspondente:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

Você pode fornecer várias cláusulas catch:

try
{
    var result = await ProcessAsync(-3, 4, cancellationToken);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Processing is cancelled.");
}

Quando ocorre uma exceção, as cláusulas catch são examinadas na ordem especificada, de cima para baixo. No máximo, apenas um bloco catch é executado para qualquer exceção gerada. Como o exemplo anterior também mostra, você pode omitir a declaração de uma variável de exceção e especificar apenas o tipo de exceção em uma cláusula catch. Uma cláusula catch sem qualquer tipo de exceção especificado corresponde a qualquer exceção e, se presente, deve ser a última cláusula catch.

Se você quiser lançar novamente uma exceção capturada, use a instrução throw, como mostra o seguinte exemplo:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
    LogError(e, "Processing failed.");
    throw;
}

Observação

throw; preserva o rastreamento de pilha original da exceção, que é armazenado na propriedade Exception.StackTrace. Ao contrário disso, throw e; atualiza a propriedade StackTrace de e.

Um filtro de exceção when

Junto com um tipo de exceção, você também pode especificar um filtro de exceção que examina ainda mais uma exceção e decide se o bloco catch correspondente manipula essa exceção. Um filtro de exceção é uma expressão booliana que segue a palavra-chave when, como mostra o seguinte exemplo:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

O exemplo anterior usa um filtro de exceção para fornecer um bloco catch para lidar com exceções de dois tipos especificados.

Você poderá fornecer várias cláusulas catch para o mesmo tipo de exceção se elas distinguirem por filtros de exceção. Uma dessas cláusulas pode não ter filtro de exceção. Se essa cláusula existir, ela deverá ser a última das cláusulas que especificam esse tipo de exceção.

Se uma cláusula catch tiver um filtro de exceção, ela poderá especificar o tipo de exceção que é igual ou menos derivado do que um tipo de exceção de uma cláusula catch que aparece depois dela. Por exemplo, se um filtro de exceção estiver presente, uma cláusula catch (Exception e) não precisará ser a última cláusula.

Exceções nos métodos assíncrono e iterador

Se ocorrer uma exceção em uma função assíncrona, ela será propagada para o chamador da função quando você aguardar o resultado da função, como mostra o seguinte exemplo:

public static async Task Run()
{
    try
    {
        Task<int> processing = ProcessAsync(-1);
        Console.WriteLine("Launched processing.");

        int result = await processing;
        Console.WriteLine($"Result: {result}.");
    }
    catch (ArgumentException e)
    {
        Console.WriteLine($"Processing failed: {e.Message}");
    }
    // Output:
    // Launched processing.
    // Processing failed: Input must be non-negative. (Parameter 'input')
}

private static async Task<int> ProcessAsync(int input)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
    }

    await Task.Delay(500);
    return input;
}

Se ocorrer uma exceção em um método iterador, ela se propagará para o chamador somente quando o iterador avançar para o próximo elemento.

A instrução try-finally

Em uma instrução try-finally, o bloco finally é executado quando o controle sai do bloco try. O controle pode deixar o bloco try como resultado de

  • execução normal,
  • execução de uma instrução de salto (ou seja, return, break, continue ou goto), ou
  • propagação de uma exceção fora do bloco try.

O exemplo a seguir usa o bloco finally para redefinir o estado de um objeto antes que o controle deixe o método:

public async Task HandleRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    finally
    {
        Busy = false;
    }
}

Você também pode usar o bloco finally para limpar recursos alocados usados no bloco try.

Observação

Quando o tipo de um recurso implementa a interface IDisposable ou IAsyncDisposable, considere a instrução using. A instrução using garante que os recursos adquiridos sejam descartados quando o controle sair da instrução using. O compilador transforma uma instrução using em uma instrução try-finally.

Em quase todos os casos, os blocos finally são executados. Os únicos casos em que os blocos finally não são executados envolvem o encerramento imediato de um programa. Por exemplo, esse encerramento pode ocorrer devido à chamada Environment.FailFast ou a uma exceção OverflowException ou InvalidProgramException. A maioria dos sistemas operacionais realiza uma limpeza razoável de recursos como parte da interrupção e do descarregamento do processo.

A instrução try-catch-finally

Use uma instrução try-catch-finally para lidar com exceções que podem ocorrer durante a execução do bloco try e especifique o código que deve ser executado quando o controle sair da instrução try:

public async Task ProcessRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    catch (Exception e) when (e is not OperationCanceledException)
    {
        LogError(e, $"Failed to process request for item ID {itemId}.");
        throw;
    }
    finally
    {
        Busy = false;
    }

}

Quando uma exceção é tratada por um bloco catch, o bloco finally é executado após a execução desse bloco catch (mesmo que outra exceção ocorra durante a execução do bloco catch). Para obter informações sobre blocos catch e finally, consulte as seções A instrução try-catch e A instrução try-finally, respectivamente.

Especificação da linguagem C#

Para obter mais informações, confira as seguintes seções da especificação da linguagem C#:

Confira também