C# Exceções
Você já deve ter visto um programa seu parar no meio e despejar na tela umas linhas em inglês começando com Unhandled exception.. Isso é uma exceção, e as linhas que começam com at são o stack trace. Nesta aula você vai entender o que é uma exceção, ler cada pedaço daquela mensagem, achar a linha do seu código no meio dela e conhecer a família das exceções do .NET. Nas próximas aulas você aprende a tratá-las.
- O que é uma exceção e a diferença para um erro de compilação.
- O que acontece quando uma exceção não é tratada: a mensagem, o código de saída
134e a saída de erro. - Como ler um stack trace: o tipo, a mensagem e cada linha
at … in arquivo:line N. - Como achar a linha do seu código num stack trace comprido.
- A família:
Exception,SystemException,ArithmeticException,ArgumentException… - As exceções que você mais vai encontrar, com as mensagens reais, e as contas erradas que não lançam exceção.
Todos os programas desta aula rodaram na máquina da Ana (usuária ana, computador notebook), com o .NET 10 (C# 14), usando dotnet run arquivo.cs na pasta ~/cs. As mensagens do compilador saem em português, mas as exceções saem em inglês: o .NET no Linux não traduz essas mensagens. A aula traduz cada uma.
A ideia antes do código
A Ana trabalha no caixa da lojinha. O normal é: o cliente traz o produto, ela passa no leitor, cobra e entrega. Mas às vezes algo sai do normal: o código de barras não existe no sistema, a maquininha recusa o cartão, acabou o troco. A Ana não pode fingir que nada aconteceu e seguir em frente. Ela para o atendimento e avisa: "este produto não está cadastrado". Alguém decide o que fazer: ela mesma, ou a gerente.
No C# é igual. Uma exceção (do latim exceptio, "o que fica fora da regra"; em inglês, exception) é um aviso de que aconteceu algo fora do normal enquanto o programa rodava: dividir um inteiro por zero, pegar a posição 10 de um array de 5, transformar "abc" em número. O .NET interrompe o que estava fazendo e lança (throws) o aviso. Se ninguém tratar o aviso, o programa termina ali.
| Erro de compilação | Exceção | |
|---|---|---|
| Quem encontra | O compilador, antes de rodar | O .NET, durante a execução |
| O programa chega a rodar? | Não: nenhuma linha é executada | Sim: roda até o ponto do problema |
| Causa típica | Código escrito errado (falta ;, tipo errado) | Código certo, mas um valor inesperado (zero, null, texto no lugar de número) |
| Como aparece | arquivo.cs(5,13): error CS0029: … (em português) | Unhandled exception. System.…Exception: … (em inglês) |
| Aula | Lendo Erros do Compilador | esta |
O programa "quebrando"
A Ana guardou as notas de cinco provas num array. As posições começam em 0, então as cinco notas estão nas posições 0, 1, 2, 3 e 4. Veja o que acontece quando o programa pede a posição 5:
int[] notas = [7, 9, 6, 10, 8];
Console.WriteLine($"Primeira nota: {notas[0]}");
Console.WriteLine($"Sexta nota: {notas[5]}");
Console.WriteLine("Fim do programa");
O código está escrito corretamente: o compilador não reclama de nada. O problema só aparece quando ele roda:
$ dotnet run notas.cs Primeira nota: 7 Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.<Main>$(String[] args) in /home/ana/cs/notas.cs:line 3 $ echo $? 134
Repare em quatro coisas:
- A linha 2 rodou:
Primeira nota: 7apareceu. O programa funcionou normalmente até o problema. - A linha 3 lançou a exceção, e o programa parou ali. Nem a própria linha 3 terminou:
Sexta nota:não apareceu. - A linha 4 nunca rodou:
Fim do programanão aparece. - O código de saída (o número que o programa devolve ao terminar, mostrado com
echo $?) foi134. Um programa que termina bem devolve0.
É isso que se chama de exceção não tratada (unhandled): ninguém cuidou do aviso, então o .NET mostra o relatório do problema e encerra o programa. A primeira linha diz exatamente isso: Unhandled exception, "exceção não tratada". Na aula try e catch, você aprende a cuidar do aviso e deixar o programa continuar.
Quando uma exceção não é tratada, o .NET encerra o programa com o sinal SIGABRT ("abortar"), que no Linux tem o número 6. A convenção do Linux para "terminou por causa de um sinal" é 128 + o número do sinal: 128 + 6 = 134. Num script do terminal, um 134 quer dizer "o programa C# quebrou com uma exceção". O número é do Linux (e do macOS); no Windows, o código de saída é outro.
Missão: o programa mostra a primeira nota e termina bem (código 0). Faça-o mostrar a quinta nota (8, na posição 4) e, na linha seguinte, tentar mostrar a sexta nota, que não existe. Rode e leia a exceção e o código de saída.
int[] notas = [7, 9, 6, 10, 8];
Console.WriteLine($"Primeira nota: {notas[0]}");
Console.WriteLine("Fim do programa");
int[] notas = [7, 9, 6, 10, 8];
Console.WriteLine($"Quinta nota: {notas[4]}");
Console.WriteLine($"Sexta nota: {notas[5]}");
Console.WriteLine("Fim do programa");
O relatório vai para a saída de erro
Um programa tem dois "canos" de saída: a saída padrão (stdout), onde vai o Console.WriteLine, e a saída de erro (stderr). Na tela, os dois aparecem misturados. O relatório da exceção vai para a saída de erro, e dá para ver isso mandando-a para um arquivo com 2>:
$ dotnet run notas.cs 2> erros.txt Primeira nota: 7 $ echo $? 134 $ cat erros.txt Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.<Main>$(String[] args) in /home/ana/cs/notas.cs:line 3
Na tela, só ficou o que o programa escreveu de propósito. O relatório foi para o erros.txt. É assim que servidores guardam os problemas num arquivo de registro (log) sem misturar com a saída normal.
Lendo o stack trace, linha por linha
O relatório se chama stack trace (em inglês, "rastro da pilha"). Parece assustador, mas tem sempre as mesmas partes. Este programa tem dois métodos numa classe estática, um chamando o outro:
Console.WriteLine("Começando");
Console.WriteLine(Calculo.Media(30, 0));
Console.WriteLine("Fim");
static class Calculo
{
public static int Dividir(int a, int b)
{
return a / b;
}
public static double Media(int soma, int quantidade)
{
return Dividir(soma, quantidade);
}
}
O programa chama Media(30, 0), que chama Dividir(30, 0), que tenta fazer 30 / 0. Divisão de inteiro por zero não existe:
$ dotnet run media.cs Começando Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Calculo.Dividir(Int32 a, Int32 b) in /home/ana/cs/media.cs:line 9 at Calculo.Media(Int32 soma, Int32 quantidade) in /home/ana/cs/media.cs:line 14 at Program.<Main>$(String[] args) in /home/ana/cs/media.cs:line 2
A primeira linha, pedaço por pedaço:
- Unhandled exception.
- "Exceção não tratada." O .NET está avisando que ninguém tratou o problema e que o programa vai terminar.
- System.DivideByZeroException
- O tipo da exceção, com o nome completo:
Systemé o namespace eDivideByZeroException("exceção de divisão por zero") é a classe. O nome sozinho já conta muito. - : Attempted to divide by zero.
- A mensagem, depois dos dois-pontos: "tentou dividir por zero". Muitas mensagens dizem também o valor que causou o problema.
Cada linha que começa com at ("em") é um degrau da pilha de chamadas:
- at Calculo.Dividir(Int32 a, Int32 b)
- Onde a exceção nasceu: no método
Dividirda classeCalculo. Entre parênteses vêm os parâmetros, com os nomes de tipo do .NET:Int32é o nome doint(aula Inteiros),Stringé o dostring. - in /home/ana/cs/media.cs:line 9
- O arquivo e a linha 9:
return a / b;. - at Calculo.Media(…) … line 14
- Quem chamou o
Dividir: o métodoMedia, na linha 14 (return Dividir(soma, quantidade);). - at Program.<Main>$(String[] args) … line 2
- Quem chamou o
Media: as instruções do topo do arquivo, na linha 2.<Main>$é o nome que o compilador dá aoMainescondido das instruções de nível superior, dentro de uma classeProgramque ele também cria (aula Program.cs e o Main). É o fundo da pilha, onde tudo começou.
Por que "pilha"?
Pense numa pilha de pratos. Quando o Main chama o Media, o .NET coloca um prato "Media" em cima do prato "Main". Quando o Media chama o Dividir, outro prato vai para o topo. O stack trace é uma foto da pilha no momento da exceção, de cima para baixo:
┌──────────────────────────────┐ │ Dividir (linha 9) ← a exceção nasceu aqui (topo) ├──────────────────────────────┤ │ Media (linha 14) ← chamou o Dividir ├──────────────────────────────┤ │ <Main>$ (linha 2) ← chamou o Media (fundo) └──────────────────────────────┘
A regra de leitura: a primeira linha at diz onde aconteceu; as de baixo dizem o caminho que levou até lá. Às vezes o erro "de verdade" não está na linha de cima. Aqui, o Dividir faz exatamente o que deveria; quem passou o zero foi a linha 2. O stack trace mostra o caminho todo para você decidir onde consertar.
- Leia o tipo (o nome que termina em
Exception). - Leia a mensagem depois dos dois-pontos. Não sabe inglês? Copie a mensagem e pesquise: ela é sempre igual, no mundo inteiro.
- Desça pelas linhas
ataté a primeira que cita um arquivo seu (in /home/ana/…) e abra essa linha. - Se a linha estiver certa, desça mais um degrau: quem chamou, e com que valores?
Missão: rode o programa e leia o stack trace. Depois conserte a chamada da linha 2 para calcular a média de 30 pontos em 2 provas. A saída deve ser Começando, 15 e Fim.
Console.WriteLine("Começando");
Console.WriteLine(Calculo.Media(30, 0));
Console.WriteLine("Fim");
static class Calculo
{
public static int Dividir(int a, int b)
{
return a / b;
}
public static double Media(int soma, int quantidade)
{
return Dividir(soma, quantidade);
}
}
Console.WriteLine("Começando");
Console.WriteLine(Calculo.Media(30, 2));
Console.WriteLine("Fim");
static class Calculo
{
public static int Dividir(int a, int b)
{
return a / b;
}
public static double Media(int soma, int quantidade)
{
return Dividir(soma, quantidade);
}
}
Nomes estranhos: funções locais
Se o método for uma função local, escrita direto no meio das instruções do topo, o nome no stack trace fica esquisito:
Console.WriteLine(Dividir(30, 0));
int Dividir(int a, int b)
{
return a / b;
}
$ dotnet run local.cs Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.<<Main>$>g__Dividir|0_0(Int32 a, Int32 b) in /home/ana/cs/local.cs:line 5 at Program.<Main>$(String[] args) in /home/ana/cs/local.cs:line 1
<<Main>$>g__Dividir|0_0 é o nome que o compilador inventou para a função local Dividir que mora dentro do <Main>$. Não precisa decorar: procure o nome que você conhece (Dividir) no meio dos símbolos, e use o in arquivo:line N, que é sempre claro.
Stack traces compridos: procure o seu arquivo
Quando a exceção nasce dentro da biblioteca do .NET, o rastro mostra também os métodos do próprio .NET. A Ana tentou transformar um texto em número com int.Parse (aula Parse e TryParse):
string idade = "29 anos"; int numero = int.Parse(idade); Console.WriteLine(numero + 1);
$ dotnet run idade.cs Unhandled exception. System.FormatException: The input string '29 anos' 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/idade.cs:line 2
As duas primeiras linhas at são do .NET (System.Number, System.Int32) e não têm in arquivo:line: você não tem o código-fonte delas, e não é lá que está o problema. A primeira linha sua é a da idade.cs:line 2, a chamada int.Parse(idade). A mensagem conta o motivo: The input string '29 anos' was not in a correct format, "o texto de entrada '29 anos' não estava num formato correto". O texto tem letras, então não vira número.
Rodando o idade.cs no editor desta página, aparecem seis linhas at System.… em vez de duas (System.Number.ParseBinaryInteger…, System.Int32.Parse(ReadOnlySpan`1 s, …)…). É o mesmo caminho: o dotnet do computador junta alguns métodos pequenos do .NET num só, para ganhar velocidade, e o editor não. A linha do seu arquivo e a mensagem são iguais nos dois.
Exceções são objetos
Uma exceção não é só uma mensagem na tela: é um objeto de verdade, criado a partir de uma classe, como os objetos de Classes e Objetos. DivideByZeroException, FormatException e todas as outras são classes. Quando o problema acontece, o .NET cria um objeto dessa classe (com a mensagem e o stack trace guardados dentro) e o lança.
Como toda classe, elas formam uma família por herança. Este programa pergunta a cada classe quem é a "mãe" dela (a propriedade BaseType, da aula Reflexão), até chegar ao topo:
Type[] tipos = [typeof(DivideByZeroException), typeof(FormatException), typeof(IndexOutOfRangeException),
typeof(ArgumentNullException), typeof(ArgumentOutOfRangeException), typeof(KeyNotFoundException),
typeof(FileNotFoundException), typeof(NullReferenceException), typeof(InvalidOperationException),
typeof(OverflowException), typeof(InvalidCastException), typeof(OutOfMemoryException), typeof(StackOverflowException)];
foreach (var t in tipos)
{
var nomes = new List<string>();
for (var x = t; x != null; x = x.BaseType) nomes.Add(x.Name);
Console.WriteLine(string.Join(" → ", nomes));
}
$ dotnet run familia.cs DivideByZeroException → ArithmeticException → SystemException → Exception → Object FormatException → SystemException → Exception → Object IndexOutOfRangeException → SystemException → Exception → Object ArgumentNullException → ArgumentException → SystemException → Exception → Object ArgumentOutOfRangeException → ArgumentException → SystemException → Exception → Object KeyNotFoundException → SystemException → Exception → Object FileNotFoundException → IOException → SystemException → Exception → Object NullReferenceException → SystemException → Exception → Object InvalidOperationException → SystemException → Exception → Object OverflowException → ArithmeticException → SystemException → Exception → Object InvalidCastException → SystemException → Exception → Object OutOfMemoryException → SystemException → Exception → Object StackOverflowException → SystemException → Exception → Object
Não se preocupe em entender cada linha deste programa agora; o que importa é a resposta. Juntando tudo, a árvore fica assim:
A família das exceções
Object
└─ Exception ......................... a mãe de todas
└─ SystemException ............... as que o próprio .NET lança
├─ ArithmeticException ....... contas
│ ├─ DivideByZeroException
│ └─ OverflowException
├─ ArgumentException ......... argumento inválido num método
│ ├─ ArgumentNullException
│ └─ ArgumentOutOfRangeException
├─ FormatException ........... texto no formato errado
├─ IndexOutOfRangeException .. posição fora do array
├─ InvalidCastException ...... conversão impossível
├─ InvalidOperationException . operação que não cabe agora
├─ KeyNotFoundException ...... chave que não existe no dicionário
├─ NullReferenceException .... usou algo que é null
├─ IOException ............... arquivos e entrada/saída
│ └─ FileNotFoundException
├─ OutOfMemoryException ...... a memória acabou
└─ StackOverflowException .... a pilha de chamadas estourou
| Classe | Em português | O que representa |
|---|---|---|
Exception | exceção | A raiz. Só objetos desta família podem ser lançados com throw e capturados com catch. |
SystemException | exceção do sistema | As que o próprio .NET lança. Você nunca cria uma SystemException diretamente. |
ArithmeticException | exceção aritmética | Problemas em contas: divisão de inteiro por zero, estouro em checked. |
ArgumentException | exceção de argumento | Um método recebeu um valor que não aceita. As filhas dizem o porquê: null (ArgumentNullException) ou fora do intervalo (ArgumentOutOfRangeException). |
Essa família importa porque, na próxima aula, um catch de uma classe mãe pega também as filhas: catch (ArithmeticException) pega DivideByZeroException e OverflowException.
O C# não tem exceções "checked". O compilador nunca obriga você a tratar ou declarar uma exceção, e não existe a palavra throws no método. Também não existe o ramo Error: problemas graves, como OutOfMemoryException, também são Exception. Cabe a você, e à documentação de cada método, saber o que pode ser lançado.
As exceções mais comuns
Estas são as que quem está começando mais encontra. Todas foram provocadas de verdade no .NET 10 da Ana; a mensagem é exatamente a que aparece. A lista para consulta está em Referência: Exceções.
| Exceção | Código que provoca | Mensagem real (e tradução) | Aula |
|---|---|---|---|
IndexOutOfRangeException | notas[5] num array de 5; "uva"[9] | Index was outside the bounds of the array. "O índice estava fora dos limites do array." | Arrays |
ArgumentOutOfRangeException | lista[5] numa List de 2 | Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') "Índice fora do intervalo. Tem de ser não negativo e menor que o tamanho da coleção." | List<T> |
ArgumentOutOfRangeException | "uva".Substring(9) | startIndex cannot be larger than length of string. (Parameter 'startIndex') "O início não pode ser maior que o tamanho do texto." | Métodos de String |
DivideByZeroException | 30 / 0 com inteiros ou decimal | Attempted to divide by zero. "Tentou dividir por zero." | Aritméticos |
FormatException | int.Parse("29 anos") | The input string '29 anos' was not in a correct format. "O texto '29 anos' não está num formato correto." | Parse e TryParse |
OverflowException | int.Parse("99999999999") | Value was either too large or too small for an Int32. "O valor é grande ou pequeno demais para um int." | Parse e TryParse |
OverflowException | checked(int.MaxValue + 1) | Arithmetic operation resulted in an overflow. "A operação aritmética resultou num estouro." | Inteiros |
ArgumentNullException | int.Parse(null) | Value cannot be null. (Parameter 's') "O valor não pode ser null (parâmetro 's')." | null |
NullReferenceException | apelido.Length com apelido = null | Object reference not set to an instance of an object. "A referência não aponta para nenhum objeto." | Referências Anuláveis |
InvalidCastException | (int)coisa com object coisa = "texto" | Unable to cast object of type 'System.String' to type 'System.Int32'. "Não dá para converter um string em int." | Conversão de Tipos |
KeyNotFoundException | precos["café"] sem a chave "café" | The given key 'café' was not present in the dictionary. "A chave 'café' não estava no dicionário." | Dictionary |
InvalidOperationException | vazio.First() numa lista vazia | Sequence contains no elements "A sequência não tem elementos." | LINQ |
InvalidOperationException | lista.Add(…) dentro de um foreach da mesma lista | Collection was modified; enumeration operation may not execute. "A coleção foi modificada; a enumeração não pode continuar." | foreach |
FileNotFoundException | File.ReadAllText("compras.txt") sem o arquivo | Could not find file '/home/ana/cs/compras.txt'. "Não foi possível encontrar o arquivo." | Arquivos |
Repare que a FileNotFoundException mostra o caminho completo: "compras.txt" virou /home/ana/cs/compras.txt, porque o programa procura o arquivo na pasta de onde foi rodado. Essa mensagem já resolve metade dos problemas com arquivos: "ah, ele estava procurando na pasta errada".
O aviso que vem antes da exceção
O compilador não consegue prever todas as exceções, mas às vezes ele avisa. Com as referências anuláveis ligadas (o padrão do .NET 10), usar algo que pode ser null dá o aviso CS8602:
string? apelido = null;
Console.WriteLine("Tamanho do apelido:");
Console.WriteLine(apelido.Length);
$ dotnet run nulo.cs /home/ana/cs/nulo.cs(3,19): warning CS8602: Desreferência de uma referência possivelmente nula. Tamanho do apelido: Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at Program.<Main>$(String[] args) in /home/ana/cs/nulo.cs:line 3
A primeira linha é do compilador, em português, antes de rodar: "desreferência (uso) de uma referência possivelmente nula", na linha 3, coluna 19. Como é só um aviso, o programa rodou… e quebrou exatamente na linha avisada. Por isso o guia insiste: leia os avisos (aula Avisos do Compilador). Cada um é uma exceção que você pode evitar antes de ela acontecer.
Nem toda conta errada lança exceção
Algumas operações que parecem impossíveis não lançam exceção. Elas devolvem um resultado estranho, e o programa segue como se nada tivesse acontecido. Isso pode ser pior, porque ninguém percebe:
double conta = 1.0 / 0; Console.WriteLine(conta); Console.WriteLine(0.0 / 0); decimal d = 0; Console.WriteLine(10m / d);
$ dotnet run real.cs ∞ NaN Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at System.Decimal.DecCalc.VarDecDiv(DecCalc& d1, DecCalc& d2) at System.Decimal.op_Division(Decimal d1, Decimal d2) at Program.<Main>$(String[] args) in /home/ana/cs/real.cs:line 5
- 1.0 / 0 → ∞
- Com
double, dividir por zero dá infinito, sem exceção. - 0.0 / 0 → NaN
- Not a Number, "não é um número": o resultado de uma conta sem sentido. Também sem exceção (aula float, double e decimal).
- 10m / d → DivideByZeroException
- Com
decimal(e com inteiros), dividir por zero lança a exceção. Mais um motivo para usardecimalcom dinheiro: o erro aparece, em vez de um "infinito" silencioso numa nota fiscal.
Com inteiros, há outro caso silencioso: o estouro. Passar do maior int faz o número "dar a volta", a menos que a conta esteja dentro de checked:
int grande = int.MaxValue; Console.WriteLine(grande + 1); Console.WriteLine(checked(grande + 1));
$ dotnet run estouro.cs -2147483648 Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow. at Program.<Main>$(String[] args) in /home/ana/cs/estouro.cs:line 3
A linha 2 mostrou um número negativo sem reclamar; a linha 3, com checked ("verificado"), lançou OverflowException. Os detalhes estão em Inteiros.
A exceção que não dá para tratar: estouro da pilha
Um método que chama a si mesmo para sempre (uma recursão sem fim) empilha pratos até a pilha acabar:
Contar(1);
void Contar(int n)
{
Contar(n + 1);
}
$ dotnet run pilha.cs Stack overflow. Repeated 261792 times: -------------------------------- at Program.<<Main>$>g__Contar|0_0(Int32) -------------------------------- at Program.<Main>$(System.String[]) $ echo $? 134
"Estouro da pilha. Repetido 261792 vezes": o .NET resume as chamadas iguais em vez de mostrar 261 mil linhas (o número muda de uma execução para outra). Esse é o único caso da aula que nenhum catch consegue segurar: o .NET encerra o programa na hora. A solução é sempre consertar o código, para a recursão ter um ponto de parada.
Prevenir ou tratar?
Muitas exceções podem ser evitadas com um if antes da linha perigosa. Quando o problema é previsível, prevenir é mais claro (e mais rápido) do que deixar a exceção acontecer:
int[] notas = [7, 9, 6, 10, 8];
int posicao = 5;
if (posicao < notas.Length)
{
Console.WriteLine($"Nota {posicao + 1}: {notas[posicao]}");
}
else
{
Console.WriteLine($"Não existe a nota {posicao + 1}: só há {notas.Length}.");
}
$ dotnet run prevenir.cs Não existe a nota 6: só há 5. $ echo $? 0
| Problema | Prevenir com | Tratar com |
|---|---|---|
| Posição fora do array | if (i < notas.Length) | catch (IndexOutOfRangeException) |
| Texto que não é número | int.TryParse(texto, out int n) | catch (FormatException) |
| Divisão por zero | if (quantidade != 0) | catch (DivideByZeroException) |
Variável null | ?., ??, if (x is not null) e os avisos CS86xx | (não trate: conserte o código) |
| Chave fora do dicionário | precos.TryGetValue("café", out var p) | catch (KeyNotFoundException) |
| Arquivo que pode sumir, rede que pode cair | (não dá para prevenir tudo: outro programa pode apagar o arquivo um instante depois do if) | catch (IOException) |
A regra prática: previna o que é erro de lógica ou de entrada do usuário; trate (com try e catch) o que depende do mundo lá fora: arquivos, rede, outros programas.
Quando dá errado
O que aconteceu: uma exceção não tratada. O código de saída é 134.
Como resolver: siga a receita: o tipo, a mensagem e a primeira linha at com in /home/ana/…:line N. Abra o arquivo nessa linha e pergunte "que valor aqui pode estar diferente do que eu esperava?".
O que aconteceu: quase sempre, você está olhando só o topo de um rastro comprido. As linhas do .NET (at System.…, sem in arquivo) vêm primeiro; as suas, mais embaixo.
Como resolver: role até o fim do relatório. A linha at Program.<Main>$(…) in …, quando existe, é a última.
O que aconteceu: a linha que lançou está certa; quem errou foi quem passou o valor. No media.cs, a linha 9 (a / b) não tem defeito; o zero veio da linha 2.
Como resolver: desça um degrau no stack trace de cada vez, até achar de onde veio o valor errado.
∞, NaN ou um número negativo absurdo
O que aconteceu: uma conta com double dividiu por zero, ou um inteiro estourou. Nenhuma exceção foi lançada.
Como resolver: confira o divisor antes da conta, use decimal para dinheiro e checked onde um estouro seria grave.
Referência
| Parte do relatório | O que é |
|---|---|
Unhandled exception. | "Exceção não tratada": ninguém capturou, o programa vai terminar |
System.FormatException | O tipo (namespace + classe) |
: The input string '…' was not … | A mensagem (sempre em inglês no Linux) |
at Classe.Metodo(Int32 a) in /home/ana/cs/x.cs:line 9 | Um degrau da pilha: método, parâmetros, arquivo e linha |
at System.… (sem in) | Código do .NET: pule até achar um arquivo seu |
Program.<Main>$ | O Main escondido das instruções de nível superior |
<<Main>$>g__Nome|0_0 | Uma função local chamada Nome |
Código de saída 134 | 128 + 6 (SIGABRT): o programa terminou por uma exceção não tratada |
2> erros.txt | O relatório vai para a saída de erro; assim ele fica num arquivo |
Stack overflow. | Recursão sem fim; não pode ser capturada |
Exercícios
1. O código de saída
Uma exceção não tratada num programa C# no Linux faz o echo $? mostrar:
2. O nome da exceção
Complete o nome da exceção que int.Parse("dez") lança:
3. A mãe de todas
Complete: todas as exceções do .NET descendem da classe
4. Num stack trace, qual linha at mostra onde a exceção nasceu?
5. Qual destas linhas não lança exceção?
6. DivideByZeroException e OverflowException são filhas de qual classe?
Missão final: o programa quebra com IndexOutOfRangeException (código 134). Sem tirar a linha que mostra a nota, previna a exceção com um if que compare posicao com notas.Length. Quando a nota não existir, mostre Não existe a nota 6. O programa deve terminar com código 0.
int[] notas = [7, 9, 6, 10, 8];
int posicao = 5;
Console.WriteLine($"Nota {posicao + 1}: {notas[posicao]}");
int[] notas = [7, 9, 6, 10, 8];
int posicao = 5;
if (posicao < notas.Length)
{
Console.WriteLine($"Nota {posicao + 1}: {notas[posicao]}");
}
else
{
Console.WriteLine($"Não existe a nota {posicao + 1}: só há {notas.Length}.");
}
Resumo
- Uma exceção é um aviso de que algo saiu do normal durante a execução. O código compila; o problema está num valor.
- Sem tratamento, o programa para na linha do problema, mostra
Unhandled exception.com o tipo, a mensagem (em inglês) e o stack trace, na saída de erro, e termina com o código134. - No stack trace, a primeira linha
até onde a exceção nasceu; as de baixo, o caminho até lá. Procure oin /home/ana/…:line Ndo seu arquivo. - Exceções são objetos de classes que herdam de
Exception. As do .NET passam porSystemException. O C# não tem exceções "checked". - As mais comuns:
IndexOutOfRangeException,ArgumentOutOfRangeException,FormatException,NullReferenceException,DivideByZeroException,KeyNotFoundException,InvalidOperationException,FileNotFoundException. doubledividido por zero dá∞ouNaN, e inteiros estouram em silêncio fora dechecked: sem exceção.- Previna com
ifeTryParseo que é previsível; trate comtry/catch(próxima aula) o que depende do mundo lá fora.