Menu

try catch em C#: tratando exceções com catch, when e finally

try, catch e finally são a forma como C# trata exceções. Veja como uma exceção sobe pela pilha de chamadas, como capturar tipos específicos de exceção na ordem certa, filtrar com when, fazer a limpeza no finally, relançar com throw; sem perder o stack trace e quais exceções não capturar.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Quando algo dá errado em tempo de execução (um arquivo não existe, um texto não é um número, uma chave não está em um dicionário), o .NET lança uma exceção: um objeto que descreve o erro. A exceção sobe pela pilha de chamadas até que um bloco catch a trate. Se nada tratar, o programa para e imprime o erro.

Um try catch básico

Envolva em try o código que pode falhar e trate a falha no catch:

Saída:

42 doubled is 84
'forty-two' is not a number
7 doubled is 14
Still running

Quando int.Parse("forty-two") lança a exceção, o Console.WriteLine depois dele no bloco try é pulado, o bloco catch (FormatException) executa e o laço continua. Você pode omitir a variável (catch (FormatException)) quando não precisa do objeto da exceção.

Esse caso específico tem uma ferramenta melhor: int.TryParse(input, out int n) retorna false em vez de lançar exceção. Exceções são para situações que o código não espera; uma entrada que muitas vezes é inválida é esperada, então verifique-a em vez de capturar.

Como uma exceção sobe

Uma exceção lançada no fundo de uma cadeia de chamadas desenrola todos os métodos até encontrar um tratador correspondente. O código depois do lançamento em cada um desses métodos não executa.

Saída:

OrderTotal finished
5.00
Caught KeyNotFoundException in Main

PriceOf e OrderTotal não têm catch, então a exceção passa direto por eles até o Main. Coloque um tratador no nível que sabe o que fazer com a falha, que muitas vezes não é onde ela aconteceu.

Capturando exceções específicas, na ordem certa

Uma cláusula catch trata o seu tipo e todos os tipos derivados dele. Quando há várias cláusulas, a primeira que corresponde vence, então ordene-as da mais específica para a mais geral:

Saída:

10 / 2 = 5
Cannot divide by zero
Both values must be whole numbers
Unexpected: OverflowException

A última chamada lança OverflowException, que nenhuma das cláusulas específicas trata, então o catch (Exception e) geral trata. Colocar catch (Exception) primeiro tornaria inalcançáveis as cláusulas depois dele, e o compilador rejeita isso com o erro CS0160.

Filtros de exceção com when

O C# 6 adicionou o when: uma condição que decide se uma cláusula catch se aplica. Se ela for false, o runtime continua procurando outro tratador como se a cláusula não existisse.

Saída:

Not found: show an empty page
Server error 503: retry later
Unhandled by Call: HTTP 401

Filtros permitem ramificar com base em dados dentro da exceção sem capturar e relançar. Eles também são a forma limpa de tratar dois tipos sem relação da mesma maneira, como faz a terceira cláusula. Um filtro executa antes de a pilha ser desenrolada, então um depurador ou um crash dump ainda mostra o estado original quando nenhum filtro corresponde.

finally: código que sempre executa

Um bloco finally executa quando o controle sai do try, seja porque ele terminou, retornou antes ou lançou uma exceção. É onde fica a limpeza. (Uma ressalva: se nada em lugar nenhum capturar a exceção, o processo pode terminar sem executá-lo.)

Saída:

Open connection
Close connection
finished
Open connection
Close connection
returned early
Open connection
Close connection
handled error

Em todos os casos, "Close connection" é impresso antes de o resultado do método chegar ao Main: o finally executa depois que o valor do return é calculado, mas antes de o método realmente retornar. Um try pode ter um finally sem nenhum catch, o que faz a limpeza e deixa a exceção seguir para quem chamou.

Para objetos que implementam IDisposable (arquivos, streams, conexões), a instrução using escreve esse try/finally para você.

Relançando: throw; vs throw e;

Às vezes um bloco catch registra ou anota algo e depois deixa a exceção continuar. A forma como você relança decide se o stack trace sobrevive:

Saída:

throw;   trace mentions LoadConfig: True
throw e; trace mentions LoadConfig: False

throw e; trata a exceção como se ela tivesse sido lançada agora no bloco catch, então os quadros abaixo dele, incluindo o método onde o erro aconteceu, somem do trace. Sempre relance com um throw; sozinho. (O atributo NoInlining só está ali porque o JIT pode fundir um método tão pequeno com quem o chama, o que o esconderia dos dois traces.)

Para acrescentar contexto, envolva a exceção em uma nova e passe a original como inner exception: throw new ConfigException("Could not start the app", e);. A inner exception mantém o próprio stack trace, e os loggers imprimem a cadeia. Escrever seus próprios tipos de exceção é assunto de lançar exceções.

O objeto Exception

Toda exceção deriva de System.Exception. Os membros que você mais usa:

MembroO que guarda
MessageUma descrição legível
GetType().NameO tipo da exceção, como FormatException
StackTraceA cadeia de chamadas de métodos no ponto do lançamento
InnerExceptionA exceção que causou esta, ou null
ToString()Tipo, mensagem, inner exceptions e stack trace juntos

Registre e.ToString() em vez de e.Message quando quiser diagnosticar uma falha depois: só a mensagem raramente diz onde estava o problema. Não mostre e.ToString() para os usuários finais.

Tipos de exceção comuns

ExceçãoCausa típica
NullReferenceExceptionChamar um membro em uma referência null
ArgumentNullExceptionUm método recebeu null onde precisa de um valor
ArgumentOutOfRangeExceptionUm argumento ou índice de lista está fora do intervalo permitido
IndexOutOfRangeExceptionUm índice de array está fora dos limites
FormatExceptionint.Parse, DateTime.Parse e parecidos em um texto com formato errado
InvalidCastExceptionUm cast explícito para um tipo que o objeto não é
InvalidOperationExceptionO objeto está no estado errado para a chamada (sequência vazia, coleção modificada)
KeyNotFoundExceptionLer com o indexador uma chave que não está no dicionário
DivideByZeroExceptionDivisão de inteiro ou decimal por zero
OverflowExceptionUm número convertido, uma conversão checked ou um resultado aritmético checked não cabe no tipo
FileNotFoundException, IOExceptionProblemas no sistema de arquivos

NullReferenceException, IndexOutOfRangeException e InvalidCastException quase sempre indicam um bug. Corrija o código em vez de capturá-las.

Não engolir exceções

Um catch vazio esconde todos os erros, inclusive os que você não previu:

try
{
    SaveOrder(order);
}
catch (Exception)
{
    // nothing: the order silently was not saved
}

O programa continua executando como se o salvamento tivesse funcionado, e a causa real se perde. Diretrizes que mantêm o tratamento de exceções honesto:

  • Capture só o que você consegue tratar, no nível que consegue tratar.
  • Se você captura para registrar, relance com throw;, a menos que o programa realmente possa continuar.
  • Capture Exception só na borda externa: Main, um tratador de requisições, um laço de worker.
  • Prefira TryParse, TryGetValue e verificações de null a exceções nos casos esperados. Lançar uma exceção é lento comparado a uma verificação, enquanto um bloco try que não lança nada quase não custa.

Erros comuns

  • catch (Exception) primeiro. As cláusulas seguintes ficam inalcançáveis (CS0160).
  • throw e; para relançar. Apaga o stack trace original; use throw;.
  • Blocos catch vazios. Os erros desaparecem; no mínimo registre e relance.
  • Usar exceções para controlar o fluxo. Valide a entrada com TryParse em vez de capturar FormatException.
  • Mostrar aos usuários o e.Message de uma exceção do framework. O texto muda entre versões do .NET e é escrito para desenvolvedores.

Perguntas frequentes

Como o try catch funciona em C#?

O código que pode falhar vai no bloco try. Se uma instrução ali lança uma exceção, o resto do bloco é pulado e o runtime procura uma cláusula catch cujo tipo corresponda à exceção, primeiro no método atual e depois em cada método que o chamou. O primeiro catch correspondente executa, e a execução continua depois da instrução try inteira.

O finally sempre executa em C#?

Quase sempre: depois que o bloco try termina normalmente, depois que um catch trata uma exceção, depois de um return ou break dentro do bloco, e quando uma exceção passa por ali a caminho de um catch mais acima na pilha de chamadas. Ele não executa quando o processo termina antes: um processo encerrado à força, Environment.FailFast, uma StackOverflowException e, no .NET Core em diante, uma exceção que nada captura, que encerra o processo antes de os blocos finally executarem.

Como capturar várias exceções em C#?

Escreva várias cláusulas catch, com o tipo mais específico primeiro: catch (FileNotFoundException) antes de catch (IOException) antes de catch (Exception). O compilador rejeita uma cláusula que nunca pode ser alcançada porque uma anterior já captura o tipo dela. Para tratar dois tipos sem relação da mesma forma, use um filtro: catch (Exception e) when (e is FormatException || e is OverflowException).

Qual a diferença entre throw e throw ex em C#?

Dentro de um catch, throw; relança a exceção atual com o stack trace original. throw ex; lança o mesmo objeto, mas reinicia o stack trace na linha atual, então o método onde o erro realmente aconteceu some do trace. Use throw;, ou envolva a exceção: throw new MyException("context", ex);.

O que é um filtro de exceção em C#?

Uma cláusula when depois de um catch (C# 6 em diante): catch (HttpRequestException e) when (e.Message.Contains("404")). O bloco catch só executa se a condição for true; caso contrário, a exceção continua procurando outro tratador como se a cláusula não existisse, e a pilha não é desenrolada.

Devo capturar Exception em C#?

Só nas bordas de um programa: o topo do Main, um tratador de requisições ou um laço em segundo plano, onde o trabalho é registrar o erro e seguir em frente ou sair de forma limpa. Nas partes internas do código, capture os tipos específicos que você realmente consegue tratar e deixe o resto se propagar.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR