C# try e catch

Na aula Exceções, você viu o programa "quebrar": uma exceção não tratada encerra tudo, com o código de saída 134. Agora você vai aprender a tratar a exceção: dizer ao C# "tente fazer isto; se der este problema, faça aquilo" e deixar o programa seguir em frente. As palavras para isso são try e catch, e o C# ainda tem um recurso a mais, os filtros when.

Você vai aprender
  • Escrever um bloco try/catch e entender o caminho que o programa percorre.
  • Capturar uma exceção específica, e o que acontece com as outras.
  • Pegar uma família inteira com a classe mãe, e por que catch (Exception) é perigoso.
  • Usar vários catch, e por que a ordem entre eles importa (CS0160).
  • Escolher com uma condição qual catch trata o problema: os filtros when.
  • Tirar informação da exceção: Message, GetType() e ToString().
  • Capturar uma exceção que nasceu dentro de outro método.
  • Por que um catch vazio ("engolir a exceção") é um dos piores hábitos.

Todos os programas rodaram na máquina da Ana, com o .NET 10 (C# 14), usando dotnet run arquivo.cs na pasta ~/cs.


A ideia antes do código

Pense num trapezista de circo. Ele tenta o salto. Quase sempre dá certo. Mas, se ele cair, há uma rede de proteção embaixo que o pega, e o espetáculo continua. Sem a rede, uma queda encerra o show.

No C#:

  • try ("tente") é o salto: o código que pode dar problema.
  • catch ("pegue", "capture") é a rede: o que fazer se uma exceção de certo tipo for lançada dentro do try.

O primeiro try/catch

A Ana recebe idades como texto (de um formulário, de um arquivo) e precisa transformá-las em número com int.Parse. Se o texto não for um número, o int.Parse lança FormatException. Desta vez, o programa vai estar preparado:

string texto = "29 anos";
try
{
    int idade = int.Parse(texto);
    Console.WriteLine($"Idade: {idade}");
}
catch (FormatException)
{
    Console.WriteLine($"Isso não é um número: {texto}");
}
Console.WriteLine("Fim do programa");
try
Abre o bloco protegido. Tudo entre as chaves logo abaixo roda normalmente, mas "vigiado".
int idade = int.Parse(texto);
A linha que pode lançar a exceção.
Console.WriteLine($"Idade: {idade}");
Só roda se a linha de cima deu certo.
catch (FormatException)
A "rede". Os parênteses dizem que tipo de exceção esta rede pega: aqui, só FormatException.
{ … }
O que fazer quando a exceção acontece: aqui, avisar com uma mensagem amigável.
Console.WriteLine("Fim do programa");
Fora do try/catch: roda nos dois casos.
$ dotnet run idade.cs
Isso não é um número: 29 anos
Fim do programa
$ echo $?
0

Compare com a aula anterior: nada de Unhandled exception, o Fim do programa apareceu e o código de saída é 0 ("terminou bem"). A exceção aconteceu do mesmo jeito, mas foi tratada.

O caminho do programa

Repare que Idade: … não apareceu. Quando uma linha dentro do try lança uma exceção, o C# abandona o resto do try e pula direto para o catch. Depois do catch, segue com o que vem depois do bloco todo. Os dois caminhos possíveis:

LinhaCom "29 anos" (exceção)Com "29" (tudo certo)
4  int.Parse(texto)lança FormatExceptionroda: idade = 29
5  WriteLine("Idade: …")puladaroda
9  WriteLine("Isso não é…")rodapulada (sem exceção, o catch não roda)
11 WriteLine("Fim…")rodaroda

Trocando "29 anos" por "29", a saída real é:

$ dotnet run idade.cs
Idade: 29
Fim do programa

Missão: rode o programa e veja-o quebrar. Depois proteja as duas linhas do meio com try e um catch (FormatException) que mostre Isso não é um número. O Fim do programa deve aparecer.

string texto = "29 anos";
int idade = int.Parse(texto);
Console.WriteLine($"Idade: {idade}");
Console.WriteLine("Fim do programa");
string texto = "29 anos";
try
{
    int idade = int.Parse(texto);
    Console.WriteLine($"Idade: {idade}");
}
catch (FormatException)
{
    Console.WriteLine($"Isso não é um número: {texto}");
}
Console.WriteLine("Fim do programa");
Para entrada do usuário, prefira TryParse

Esta aula usa int.Parse porque é o jeito mais simples de provocar uma exceção. Num programa de verdade, quando o texto vem do teclado, int.TryParse é melhor: ele não lança exceção, só devolve false (aula Parse e TryParse). O fim desta aula compara as duas formas. O try/catch brilha mesmo com o que você não consegue prever: arquivos, rede, outros programas.


O catch só pega o tipo que você pediu

A rede do catch tem formato: catch (FormatException) pega só FormatException (e as classes filhas dela). Se outra exceção acontecer no try, ela passa direto pela rede:

int soma = 22;
int provas = 0;
try
{
    Console.WriteLine($"Média: {soma / provas}");
}
catch (FormatException)
{
    Console.WriteLine("Número inválido");
}
Console.WriteLine("Fim do programa");
$ dotnet run media.cs
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
   at Program.<Main>$(String[] args) in /home/ana/cs/media.cs:line 5

A divisão lançou DivideByZeroException, que não é uma FormatException. O catch foi ignorado, a exceção continuou "caindo" e encerrou o programa, como se o try nem existisse.

Pegar a classe mãe pega as filhas

Lembra da família? DivideByZeroException e OverflowException são filhas de ArithmeticException. Um catch da classe mãe pega qualquer filha. E, para saber qual foi o problema, dê um nome à exceção capturada:

int soma = 22;
int provas = 0;
try
{
    Console.WriteLine($"Média: {soma / provas}");
}
catch (ArithmeticException e)
{
    Console.WriteLine($"Problema: {e.Message}");
}
Console.WriteLine("Fim do programa");
$ dotnet run media.cs
Problema: Attempted to divide by zero.
Fim do programa
catch (ArithmeticException e)
Pega ArithmeticException e todas as filhas. O e é uma variável que recebe o objeto da exceção. Por costume ela se chama e (de exception) ou ex, mas pode ter qualquer nome.
e.Message
A mensagem da exceção, a mesma que aparece depois dos dois-pontos no relatório.

Sem precisar do objeto, escreva só o tipo, como no primeiro exemplo: catch (FormatException). Se você der um nome e não usar, o compilador avisa (CS0168, veja Quando dá errado).

Subindo mais, catch (Exception e) pega qualquer exceção, e catch sozinho, sem parênteses, também:

try
{
    int[] v = [1];
    Console.WriteLine(v[3]);
}
catch
{
    Console.WriteLine("Deu algum problema.");
}
$ dotnet run semtipo.cs
Deu algum problema.

Parece prático, mas é perigoso: você passa a tratar do mesmo jeito problemas muito diferentes, inclusive os que você nem imaginava (um NullReferenceException causado por um defeito no seu código, por exemplo). E a mensagem "Deu algum problema" não ajuda ninguém a consertar. A regra dos programadores experientes: capture a exceção mais específica que você sabe tratar. catch (Exception e) fica para um lugar só, no ponto mais alto do programa, para registrar o que escapou de tudo.


Vários catch

Um try pode ter vários catch, um para cada tipo de problema, cada um com a sua resposta. O C# testa os catch de cima para baixo e usa o primeiro que combinar:

string[] textos = ["10", "abc", "0", "99999999999"];
foreach (string t in textos)
{
    try
    {
        int n = int.Parse(t);
        Console.WriteLine($"100 / {n} = {100 / n}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{t}\" não é número");
    }
    catch (DivideByZeroException)
    {
        Console.WriteLine("Não dá para dividir por zero");
    }
    catch (OverflowException)
    {
        Console.WriteLine($"{t} é grande demais para um int");
    }
}
$ dotnet run divisoes.cs
100 / 10 = 10
"abc" não é número
Não dá para dividir por zero
99999999999 é grande demais para um int

O foreach passa pelos quatro textos. Cada volta tem o seu próprio try: "10" dá certo; "abc" cai no primeiro catch; "0" passa pelo int.Parse, mas a divisão cai no segundo; "99999999999" não cabe num int e cai no terceiro. E, como o try está dentro do laço, um problema numa volta não impede as outras.

A ordem importa

Como o C# usa o primeiro catch que combina, um catch da classe mãe antes de uma filha deixaria a filha inalcançável. O compilador percebe e recusa o programa:

Este código tem um erro de propósito.
try
{
    int n = int.Parse("abc");
    Console.WriteLine(100 / n);
}
catch (Exception e)
{
    Console.WriteLine("Algo deu errado");
}
catch (FormatException e)
{
    Console.WriteLine("Não é número");
}
$ dotnet run ordem.cs
/home/ana/cs/ordem.cs(10,8): error CS0160: Cláusula catch anterior já captura todas as exceções desta ou de um super tipo ("Exception")
/home/ana/cs/ordem.cs(6,18): warning CS0168: A variável "e" está declarada, mas nunca é usada
/home/ana/cs/ordem.cs(10,24): warning CS0168: A variável "e" está declarada, mas nunca é usada

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

"Uma cláusula catch anterior já captura todas as exceções deste tipo ou de um supertipo (Exception)." Em inglês: A previous catch clause already catches all exceptions of this or of a super type. O catch (Exception e) da linha 6 já pega qualquer FormatException, então o da linha 10 nunca seria usado. Os dois avisos CS0168 dizem que os dois e não são usados. Regra: do mais específico (filha) para o mais geral (mãe). Inverter os dois catch resolve.

Missão: o programa trata texto inválido, mas quebra no "0". Acrescente um segundo catch, para DivideByZeroException, que mostre Não dá para dividir por zero, e faça o Fim aparecer.

string[] textos = ["10", "abc", "0"];
foreach (string t in textos)
{
    try
    {
        int n = int.Parse(t);
        Console.WriteLine($"100 / {n} = {100 / n}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{t}\" não é número");
    }
}
Console.WriteLine("Fim");
string[] textos = ["10", "abc", "0"];
foreach (string t in textos)
{
    try
    {
        int n = int.Parse(t);
        Console.WriteLine($"100 / {n} = {100 / n}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{t}\" não é número");
    }
    catch (DivideByZeroException)
    {
        Console.WriteLine("Não dá para dividir por zero");
    }
}
Console.WriteLine("Fim");

Filtros: catch … when (condição) C# 6

Às vezes o tipo da exceção não basta para decidir o que fazer. Um texto vazio e um texto com letras dão a mesma FormatException, mas a Ana quer responder diferente a cada um. O filtro when ("quando") acrescenta uma condição ao catch: ele só pega a exceção se o tipo combinar e a condição for true:

string[] entradas = ["12", "", "doze", "99999999999"];
foreach (string texto in entradas)
{
    try
    {
        int quantidade = int.Parse(texto);
        Console.WriteLine($"Quantidade: {quantidade}");
    }
    catch (FormatException) when (texto == "")
    {
        Console.WriteLine("Você não digitou nada.");
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{texto}\" não é um número.");
    }
    catch (Exception e) when (e is OverflowException or ArgumentNullException)
    {
        Console.WriteLine($"Valor fora do esperado: {e.GetType().Name}");
    }
}
$ dotnet run filtro.cs
Quantidade: 12
Você não digitou nada.
"doze" não é um número.
Valor fora do esperado: OverflowException
catch (FormatException) when (texto == "")
"Pegue a FormatException quando o texto estiver vazio." Para "", as duas partes combinam, e este catch roda.
catch (FormatException)
Para "doze", o filtro de cima deu false, então o C# continua procurando e chega neste, que pega qualquer FormatException. Dois catch do mesmo tipo são permitidos quando o primeiro tem filtro.
catch (Exception e) when (e is OverflowException or ArgumentNullException)
Um catch para dois tipos. e is OverflowException pergunta "a exceção é uma OverflowException?", e o or ("ou") junta as duas perguntas (aula Pattern Matching). As outras exceções passam direto.
e.GetType().Name
O nome da classe da exceção, sem o namespace: OverflowException.
Vindo do Java?

O C# não tem o multi-catch com barra (catch (A | B e)). A forma do C# é a do último catch acima: catch (Exception e) when (e is A or B).

Um detalhe que diferencia o filtro de um if dentro do catch: se a condição do when dá false, é como se aquele catch não existisse, e o C# continua procurando outra rede, nos catch de baixo ou em quem chamou o método. Com um if lá dentro, a exceção já teria sido capturada.

Com filtro, a regra da ordem também muda: um catch (Exception e) when (…) pode vir antes de um catch (FormatException), porque o filtro pode deixar a exceção passar. O compilador aceita; cabe a você pensar na ordem.

Missão: o programa responde "" não é um número. para o texto vazio, o que fica estranho. Acrescente, antes do catch que já existe, um catch (FormatException) when (…) que, para o texto vazio, mostre Você não digitou nada.

string[] entradas = ["12", "", "doze"];
foreach (string texto in entradas)
{
    try
    {
        int quantidade = int.Parse(texto);
        Console.WriteLine($"Quantidade: {quantidade}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{texto}\" não é um número.");
    }
}
string[] entradas = ["12", "", "doze"];
foreach (string texto in entradas)
{
    try
    {
        int quantidade = int.Parse(texto);
        Console.WriteLine($"Quantidade: {quantidade}");
    }
    catch (FormatException) when (texto == "")
    {
        Console.WriteLine("Você não digitou nada.");
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{texto}\" não é um número.");
    }
}

O que fazer com a exceção capturada

A variável e é um objeto, com propriedades e métodos úteis. Veja os principais lado a lado:

try
{
    int n = int.Parse("12x");
}
catch (FormatException e)
{
    Console.WriteLine($"Message:  {e.Message}");
    Console.WriteLine($"Tipo:     {e.GetType().Name}");
    Console.WriteLine($"Completo: {e.GetType().FullName}");
    Console.WriteLine("--- ToString() ---");
    Console.WriteLine(e.ToString());
    Console.WriteLine("--- o programa continua ---");
}
$ dotnet run info.cs
Message:  The input string '12x' was not in a correct format.
Tipo:     FormatException
Completo: System.FormatException
--- ToString() ---
System.FormatException: The input string '12x' was not in a correct format.
   at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
   at System.Int32.Parse(String s)
   at Program.<Main>$(String[] args) in /home/ana/cs/info.cs:line 3
--- o programa continua ---
MembroDevolveQuando usar
e.MessageSó a mensagem: The input string '12x' was not in a correct format.Para mostrar um detalhe do problema. Em inglês: para o usuário final, prefira uma frase sua.
e.GetType().NameSó o nome da classe: FormatExceptionMensagens curtas.
e.GetType().FullNameCom o namespace: System.FormatExceptionPara registrar sem ambiguidade.
e.ToString()Tipo, mensagem e o stack trace, igual ao relatório de uma exceção não tratadaPara registrar o problema completo num arquivo de log, enquanto você desenvolve.
e.StackTraceSó as linhas at …Raramente sozinho: o ToString() já inclui.

Repare: o ToString() mostra o mesmo relatório da exceção não tratada, só que sem o Unhandled exception. na frente, e o programa continua. Para mandar esse relatório para a saída de erro, como o .NET faz, use Console.Error.WriteLine(e).


A exceção sobe pelos métodos

O try não precisa estar no mesmo método da linha que lança. Uma exceção não tratada dentro de um método sobe para quem o chamou, depois para quem chamou esse, e assim por diante, degrau por degrau da pilha, até encontrar um catch que combine. É o mesmo caminho do stack trace, de cima para baixo:

try
{
    Console.WriteLine(Calculo.Media(30, 0));
}
catch (DivideByZeroException e)
{
    Console.WriteLine($"Não deu para calcular a média: {e.Message}");
}
Console.WriteLine("Fim");

static class Calculo
{
    public static int Dividir(int a, int b) => a / b;
    public static double Media(int soma, int quantidade) => Dividir(soma, quantidade);
}
$ dotnet run sobe.cs
Não deu para calcular a média: Attempted to divide by zero.
Fim

A exceção nasceu no Dividir, que não tem try. Subiu para o Media, que também não tem. Subiu para as instruções do topo, onde o catch (DivideByZeroException) a pegou. Se ninguém no caminho tivesse um catch adequado, ela chegaria ao fundo da pilha e o programa terminaria com 134. Isso permite tratar o problema onde faz sentido: o Dividir não sabe o que mostrar ao usuário; quem chamou sabe.


Um uso clássico: pedir de novo até acertar

Juntando try/catch com um while, o programa insiste até a pessoa digitar um número válido (a leitura do teclado está na aula Console.ReadLine):

int idade = -1;
while (idade < 0)
{
    Console.Write("Sua idade: ");
    string linha = Console.ReadLine() ?? "";
    try
    {
        idade = int.Parse(linha);
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{linha}\" não é um número. Tente de novo.");
    }
}
Console.WriteLine($"Ano que vem você terá {idade + 1} anos.");
$ dotnet run repete.cs
Sua idade: vinte
"vinte" não é um número. Tente de novo.
Sua idade: 29
Ano que vem você terá 30 anos.

A idade começa em -1 (um valor impossível) para o laço rodar pelo menos uma vez. Se o int.Parse falhar, a atribuição idade = … não acontece, a idade continua -1 e o laço repete. O ?? "" troca um null (fim da entrada) por texto vazio (aula Operadores ??, ?. e ??=).

O mesmo programa com TryParse

Para entrada do usuário, a forma preferida em C# dispensa a exceção:

int idade;
Console.Write("Sua idade: ");
while (!int.TryParse(Console.ReadLine(), out idade))
{
    Console.Write("Isso não é um número. Sua idade: ");
}
Console.WriteLine($"Ano que vem você terá {idade + 1} anos.");
$ dotnet run repete2.cs
Sua idade: vinte
Isso não é um número. Sua idade: 29
Ano que vem você terá 30 anos.

Mais curto, sem try e sem o valor -1 inventado. Lançar e capturar uma exceção dá mais trabalho ao computador do que um if; num laço que lê milhares de linhas de um arquivo, a diferença aparece. Guarde o try/catch para o que não dá para checar antes.


Não engula exceções

"Engolir" uma exceção é capturá-la e não fazer nada. O compilador aceita, o programa não quebra… e é exatamente esse o problema. A Ana somou os preços de uma lista:

Este código esconde um problema de propósito.
string[] precos = ["10", "25", "sete e cinquenta", "8"];
decimal total = 0;
foreach (string p in precos)
{
    try
    {
        total += decimal.Parse(p);
    }
    catch (FormatException)
    {
    }
}
Console.WriteLine($"Total: {total}");
$ dotnet run engole.cs
Total: 43

O total certo seria 50,50. O "sete e cinquenta" lançou FormatException, o catch vazio "engoliu", e o programa mostrou um número errado sem avisar ninguém. Um programa que quebra é chato; um programa que dá a resposta errada em silêncio é muito pior, porque ninguém percebe.

Se você capturou, faça pelo menos uma destas coisas:

  • Avise: Console.WriteLine($"Preço inválido ignorado: {p}");
  • Corrija: use um valor padrão que faça sentido, ou peça de novo.
  • Registre: Console.Error.WriteLine(e);, com o relatório completo.
  • Não capture: se você não sabe o que fazer com a exceção ali, deixe-a subir para quem sabe, ou relance com throw; (aula throw).

Formas diferentes, qual usar

SituaçãoForma
Um tipo de problema, uma respostatry { … } catch (TipoException) { … }
Precisa da mensagem ou do relatóriocatch (TipoException e) e use e.Message ou e.ToString()
Vários problemas, respostas diferentesVários catch, do mais específico ao mais geral
Vários tipos, a mesma respostacatch (Exception e) when (e is A or B), ou o catch da mãe comum (ArithmeticException)
Mesmo tipo, respostas diferentes conforme a situaçãoFiltro: catch (TipoException) when (condição)
Algo que precisa rodar com ou sem exceçãofinally (próxima aula) ou using (using e IDisposable)
Você não sabe tratar aliNão capture; deixe subir
Dá para evitar com um if ou TryParsePrefira o if/TryParse: if (provas != 0) é mais claro que capturar DivideByZeroException
Registrar tudo o que escapou, no topo do programaUm único catch (Exception e) que registra e.ToString()

Quando dá errado

CS0103 com uma variável criada no try
try
{
    int idade = int.Parse("29");
}
catch (FormatException)
{
    Console.WriteLine("Não é número");
}
Console.WriteLine($"Idade: {idade}");
$ dotnet run escopo.cs
/home/ana/cs/escopo.cs(9,29): error CS0103: O nome "idade" não existe no contexto atual

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: as chaves do try são um bloco, e uma variável criada dentro de um bloco só existe lá dentro (aula Escopo). Na linha 9, idade já não existe.

Como consertar: declare a variável antes do try e só faça a atribuição dentro dele, ou coloque o WriteLine dentro do try. Mas cuidado com o próximo erro.

CS0165: "Uso de variável local não atribuída"
int idade;
try
{
    idade = int.Parse("29");
}
catch (FormatException)
{
    Console.WriteLine("Não é número");
}
Console.WriteLine($"Idade: {idade}");
$ dotnet run escopo2.cs
/home/ana/cs/escopo2.cs(10,29): error CS0165: Uso de variável local não atribuída "idade"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: agora a variável existe, mas o compilador pensou no caminho com exceção: se o int.Parse falhar, a atribuição não acontece, o catch roda, e a linha 10 usaria uma idade sem valor. O C# não deixa.

Como consertar: dê um valor inicial na declaração (int idade = 0;), ou dê um valor também dentro do catch.

CS0160: catch da mãe antes da filha

O que aconteceu: um catch de uma classe mãe (Exception, ArithmeticException) veio antes do catch de uma filha, sem filtro. O de baixo nunca seria usado.

Como consertar: coloque os catch do mais específico para o mais geral.

Aviso CS0168: "A variável "e" está declarada, mas nunca é usada"

O que aconteceu: você escreveu catch (FormatException e) e não usou o e.

Como consertar: se não precisa do objeto, tire o nome: catch (FormatException). Se precisa, use-o (e.Message).

O catch existe, mas o programa quebrou mesmo assim

O que aconteceu: a exceção lançada não é do tipo do catch (nem filha dele), ou o filtro when deu false. Também pode ser que a linha que lançou esteja fora do try.

Como consertar: leia a primeira linha do relatório: o tipo que aparece ali é o que você precisa capturar. Confira o número da linha: ela está entre as chaves do try?

O programa não quebra, mas o resultado está errado

O que aconteceu: provavelmente há um catch vazio engolindo exceções. Procure por catch seguido de { } no código.

Como consertar: coloque pelo menos um aviso ou Console.Error.WriteLine(e) dentro dele e rode de novo.


Referência

SintaxeO que faz
try { … }Bloco vigiado: se algo lança exceção, o resto dele é pulado
catch (Tipo) { … }Roda se o try lançou Tipo ou uma filha dele
catch (Tipo e) { … }O mesmo, com o objeto da exceção na variável e
catch { … }Pega qualquer exceção, sem o objeto (evite)
catch (A) { … } catch (B) { … }Vários tratamentos; o primeiro que combina é usado
catch (Tipo) when (condição) { … }Filtro: só pega se a condição for true C# 6
catch (Exception e) when (e is A or B)O mesmo tratamento para dois tipos
e.MessageA mensagem
e.GetType().NameO nome da classe da exceção
e.ToString()Tipo + mensagem + stack trace
Console.Error.WriteLine(e)Escreve o relatório na saída de erro e continua
CS0160catch de filha depois do da mãe
CS0165Variável usada depois do try pode estar sem valor
CS0168 (aviso)A variável da exceção não é usada

Exercícios

1. A palavra que falta

Complete para capturar o problema do int.Parse:

try { n = int.Parse(t); } (FormatException) { … }

2. Só a mensagem

Complete para mostrar só a mensagem da exceção:

Console.WriteLine(e.);

3. O filtro

Complete com a palavra que acrescenta uma condição ao catch:

catch (FormatException) (texto == "")

4. O que este trecho mostra?

try
{
    Console.WriteLine("A");
    int x = int.Parse("x");
    Console.WriteLine("B");
}
catch (FormatException)
{
    Console.WriteLine("C");
}
Console.WriteLine("D");

5. Um try lança DivideByZeroException, e o único catch é catch (FormatException). O que acontece?

6. Qual ordem de catch (sem filtros) compila?

Missão final: o programa pede a idade até receber um número, mas quebra na primeira resposta (vinte). Proteja o int.Parse com try e um catch (FormatException) que mostre "…" não é um número. Tente de novo.. A entrada já vem preenchida: vinte e depois 29.

int idade = -1;
while (idade < 0)
{
    Console.Write("Sua idade: ");
    string linha = Console.ReadLine() ?? "";
    idade = int.Parse(linha);
}
Console.WriteLine($"Ano que vem você terá {idade + 1} anos.");
int idade = -1;
while (idade < 0)
{
    Console.Write("Sua idade: ");
    string linha = Console.ReadLine() ?? "";
    try
    {
        idade = int.Parse(linha);
    }
    catch (FormatException)
    {
        Console.WriteLine($"\"{linha}\" não é um número. Tente de novo.");
    }
}
Console.WriteLine($"Ano que vem você terá {idade + 1} anos.");

Resumo

  • try marca o código que pode lançar exceção; catch (Tipo) diz o que fazer se ela vier. Com catch (Tipo e), você recebe o objeto da exceção.
  • Quando uma linha do try lança, o resto do try é pulado, o catch roda e o programa continua depois do bloco, com código de saída 0.
  • O catch pega o tipo pedido e as classes filhas; outros tipos passam direto e sobem.
  • Vários catch: do mais específico ao mais geral, senão CS0160.
  • Filtro when (condição): o catch só pega se a condição for verdadeira. when (e is A or B) substitui o multi-catch de outras linguagens.
  • e.Message dá a mensagem; e.ToString(), o relatório completo.
  • A exceção sobe pelos métodos até achar um catch: trate onde faz sentido.
  • Para entrada do usuário, TryParse é melhor que Parse + catch. Nunca deixe um catch vazio.