C# throw
Até agora, quem lançava as exceções era o .NET: o int.Parse, a divisão por zero, o array. Nesta aula é a sua vez. Com a palavra throw ("lançar", "atirar"), o seu método avisa que recebeu algo que não dá para aceitar, do mesmo jeito que os métodos do .NET fazem. Você também vai aprender a relançar uma exceção sem perder o rastro dela (throw; × throw ex;), a embrulhar uma exceção em outra e a usar throw no meio de uma expressão.
- Lançar uma exceção com
throw new …, e quem a recebe. - Qual exceção do .NET escolher:
ArgumentException,ArgumentNullException,ArgumentOutOfRangeException,InvalidOperationException,NotImplementedException… - Os atalhos de validação:
ArgumentNullException.ThrowIfNull,ArgumentOutOfRangeException.ThrowIfNegativee companhia. - Relançar dentro de um
catch: por quethrow;e nãothrow ex;(o avisoCA2200). - Embrulhar uma exceção em outra com
InnerException. - As expressões throw:
?? throw, no ternário, noswitche depois de=>. - Os erros
CS0029,CS0156eCS8115.
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
A Ana é a gerente da lojinha, e a funcionária nova está no caixa. Um cliente pede 150% de desconto. A funcionária não deve "dar um jeito" (cobrar um valor negativo, inventar um desconto qualquer): ela deve parar e chamar a gerente, dizendo exatamente o problema: "o desconto pedido é 150%, e o máximo é 100%". Quem sabe o que fazer com isso é a gerente.
O throw é esse "chamar a gerente". Um método que recebe um valor impossível lança uma exceção com uma mensagem clara, e quem chamou o método decide: trata com catch, ou deixa subir.
1. Lançar uma exceção: throw new …
Console.WriteLine(Loja.AplicarDesconto(80m, 10));
Console.WriteLine(Loja.AplicarDesconto(80m, 150));
Console.WriteLine("Fim");
static class Loja
{
public static decimal AplicarDesconto(decimal preco, int percentual)
{
if (percentual < 0 || percentual > 100)
{
throw new ArgumentOutOfRangeException(nameof(percentual), percentual, "O desconto vai de 0 a 100.");
}
return preco - preco * percentual / 100;
}
}
$ dotnet run desconto.cs 72 Unhandled exception. System.ArgumentOutOfRangeException: O desconto vai de 0 a 100. (Parameter 'percentual') Actual value was 150. at Loja.AplicarDesconto(Decimal preco, Int32 percentual) in /home/ana/cs/desconto.cs:line 11 at Program.<Main>$(String[] args) in /home/ana/cs/desconto.cs:line 2 $ echo $? 134
- if (percentual < 0 || percentual > 100)
- A verificação: o desconto só faz sentido de 0 a 100.
- throw
- "Lance." Interrompe o método na hora, como um
return, mas em vez de devolver um valor, lança a exceção para quem chamou. A linha doreturnnão roda. - new ArgumentOutOfRangeException(…)
- Cria o objeto da exceção, como qualquer
new(aula Classes e Objetos). "Argumento fora do intervalo" é a exceção certa para um número fora dos limites. - nameof(percentual)
- O nome do parâmetro, como texto:
"percentual". Usarnameofem vez de escrever o texto à mão garante que, se você renomear o parâmetro, a mensagem acompanha. Aparece no relatório como(Parameter 'percentual'). - percentual
- O valor recebido. Aparece como
Actual value was 150.("o valor recebido foi 150"). - "O desconto vai de 0 a 100."
- A mensagem. A sua mensagem pode ser em português: ela aparece exatamente como você escreveu.
O relatório é igual aos do .NET: tipo, mensagem e stack trace. A primeira linha at aponta a linha 11, a do throw; a de baixo, a linha 2, de onde veio o 150. Para quem chama, não há diferença entre uma exceção lançada pelo .NET e uma lançada por você: dá para capturar com catch (ArgumentOutOfRangeException e), ler a e.Message etc.
O método poderia fazer Console.WriteLine("Desconto inválido") e devolver o preço sem desconto. Mas aí quem chamou nunca fica sabendo que deu errado, e o programa segue com um valor que ninguém pediu. Um método não sabe onde está sendo usado (num programa de console, numa página web, num teste automático) nem o que é melhor fazer em cada caso. O throw entrega o problema a quem sabe.
Missão: o método Vender aceita quantidade negativa, e o estoque "cresce" numa venda. Faça-o lançar uma ArgumentOutOfRangeException quando a quantidade for menor ou igual a zero, com a mensagem A quantidade tem de ser positiva.
int estoque = 10;
estoque = Vender(estoque, 3);
Console.WriteLine($"Estoque: {estoque}");
estoque = Vender(estoque, -5);
Console.WriteLine($"Estoque: {estoque}");
int Vender(int emEstoque, int quantidade)
{
return emEstoque - quantidade;
}
int estoque = 10;
estoque = Vender(estoque, 3);
Console.WriteLine($"Estoque: {estoque}");
estoque = Vender(estoque, -5);
Console.WriteLine($"Estoque: {estoque}");
int Vender(int emEstoque, int quantidade)
{
if (quantidade <= 0)
{
throw new ArgumentOutOfRangeException(nameof(quantidade), quantidade, "A quantidade tem de ser positiva.");
}
return emEstoque - quantidade;
}
2. Qual exceção lançar
O .NET já tem exceções prontas para os problemas mais comuns. Prefira sempre uma delas, porque todo programador C# sabe o que cada uma quer dizer:
try
{
throw new ArgumentException("O nome não pode ficar vazio.");
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
}
try
{
throw new ArgumentException("O nome não pode ficar vazio.", "nome");
}
catch (ArgumentException e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.ParamName);
}
try
{
throw new InvalidOperationException("O caixa está fechado.");
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}
try
{
throw new ArgumentNullException("cliente");
}
catch (ArgumentNullException e)
{
Console.WriteLine(e.Message);
}
$ dotnet run simples.cs O nome não pode ficar vazio. O nome não pode ficar vazio. (Parameter 'nome') nome O caixa está fechado. Value cannot be null. (Parameter 'cliente')
Repare na ordem dos argumentos, que pega muita gente: no ArgumentException, a mensagem vem primeiro e o nome do parâmetro depois; no ArgumentNullException e no ArgumentOutOfRangeException, o nome do parâmetro vem primeiro. Com um argumento só, o ArgumentNullException monta a mensagem sozinho, em inglês.
| Exceção | Quando lançar | Exemplo |
|---|---|---|
ArgumentNullException | Um parâmetro veio null e não podia | Cadastrar(null) |
ArgumentOutOfRangeException | Um número (ou posição) fora dos limites | Desconto de 150%, idade −1 |
ArgumentException | Outro problema com um parâmetro | Nome vazio, e-mail sem @ |
InvalidOperationException | Os parâmetros estão certos, mas o objeto não está num estado que permita a operação | Vender com o caixa fechado; tirar de uma fila vazia |
NotImplementedException | O método ainda não foi escrito (lembrete temporário) | CalcularFrete() => throw new NotImplementedException(); |
NotSupportedException | A operação não existe para este objeto, de propósito | Escrever numa lista só de leitura |
| Uma exceção sua | Um problema do seu domínio, que alguém vai querer capturar separado | SaldoInsuficienteException (aula Exceções Próprias) |
Exception e SystemException (gerais demais: quem chama não consegue capturar só o seu problema sem capturar todos), NullReferenceException, IndexOutOfRangeException e StackOverflowException (são do próprio .NET e indicam defeitos; use as Argument…Exception no lugar).
3. Os atalhos de validação .NET 6+
Verificar parâmetros no começo dos métodos é tão comum que o .NET ganhou métodos prontos que fazem o if e o throw numa linha só, e ainda descobrem sozinhos o nome do parâmetro:
void Cadastrar(string? nome, int idade, decimal saldo)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nome);
ArgumentOutOfRangeException.ThrowIfNegative(idade);
ArgumentOutOfRangeException.ThrowIfGreaterThan(idade, 130);
ArgumentOutOfRangeException.ThrowIfNegative(saldo);
Console.WriteLine($"{nome}, {idade} anos: ok");
}
void Testar(Action acao)
{
try { acao(); }
catch (Exception e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }
}
Testar(() => Cadastrar("Ana", 29, 10m));
Testar(() => Cadastrar(" ", 29, 10m));
Testar(() => Cadastrar(null, 29, 10m));
Testar(() => Cadastrar("Ana", -1, 10m));
Testar(() => Cadastrar("Ana", 200, 10m));
Testar(() => Cadastrar("Ana", 29, -5m));
string? cliente = null;
Testar(() => ArgumentNullException.ThrowIfNull(cliente));
$ dotnet run guarda.cs
Ana, 29 anos: ok
ArgumentException: The value cannot be an empty string or composed entirely of whitespace. (Parameter 'nome')
ArgumentNullException: Value cannot be null. (Parameter 'nome')
ArgumentOutOfRangeException: idade ('-1') must be a non-negative value. (Parameter 'idade')
Actual value was -1.
ArgumentOutOfRangeException: idade ('200') must be less than or equal to '130'. (Parameter 'idade')
Actual value was 200.
ArgumentOutOfRangeException: saldo ('-5') must be a non-negative value. (Parameter 'saldo')
Actual value was -5.
ArgumentNullException: Value cannot be null. (Parameter 'cliente')
O Testar é só um ajudante desta página: ele roda cada chamada dentro de um try e mostra o tipo e a mensagem, para caberem todos os casos num programa só (o () => … é uma lambda, "um pedaço de código guardado para rodar depois").
| Atalho | Lança quando | Desde |
|---|---|---|
ArgumentNullException.ThrowIfNull(x) | x é null | .NET 6 |
ArgumentException.ThrowIfNullOrEmpty(texto) | texto null ou "" | .NET 7 |
ArgumentException.ThrowIfNullOrWhiteSpace(texto) | texto null, vazio ou só com espaços | .NET 8 |
ArgumentOutOfRangeException.ThrowIfNegative(n) | n < 0 | .NET 8 |
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(n) | n <= 0 | .NET 8 |
ArgumentOutOfRangeException.ThrowIfZero(n) | n == 0 | .NET 8 |
ArgumentOutOfRangeException.ThrowIfGreaterThan(n, max) | n > max | .NET 8 |
ArgumentOutOfRangeException.ThrowIfLessThan(n, min) | n < min | .NET 8 |
A desvantagem: a mensagem é a padrão, em inglês. Quando a mensagem importa (vai aparecer para alguém), use o if com throw new e escreva a sua.
4. Relançar: throw; × throw ex;
Às vezes um catch quer fazer alguma coisa com a exceção (registrar num log, por exemplo) e depois deixá-la continuar subindo, porque não sabe resolvê-la. Para isso, dentro do catch, escreva throw; sozinho, sem nada depois:
Estoque.Carregar("doze");
static class Estoque
{
public static void Carregar(string texto)
{
try
{
int quantidade = Ler(texto);
Console.WriteLine($"Quantidade: {quantidade}");
}
catch (FormatException)
{
Console.WriteLine($"[log] falhou ao ler \"{texto}\"");
throw;
}
}
static int Ler(string texto)
{
return int.Parse(texto);
}
}
$ dotnet run relanca.cs [log] falhou ao ler "doze" Unhandled exception. System.FormatException: The input string 'doze' was not in a correct format. at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value) at System.Int32.Parse(String s) at Estoque.Ler(String texto) in /home/ana/cs/relanca.cs:line 21 at Estoque.Carregar(String texto) in /home/ana/cs/relanca.cs:line 9 at Program.<Main>$(String[] args) in /home/ana/cs/relanca.cs:line 1
O catch escreveu o log e o throw; relançou a mesma exceção, com o rastro intacto: dá para ver que ela nasceu no int.Parse, chamado pelo Ler na linha 21, chamado pelo Carregar na linha 9.
Agora a versão que muita gente escreve, dando nome à exceção e relançando com throw e;:
catch (FormatException e)
{
Console.WriteLine($"[log] falhou ao ler \"{texto}\"");
throw e;
}
$ dotnet run relanca2.cs /home/ana/cs/relanca2.cs(15,13): warning CA2200: Gerar novamente uma exceção detectada altera as informações de pilha (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200) [log] falhou ao ler "doze" Unhandled exception. System.FormatException: The input string 'doze' was not in a correct format. at Estoque.Carregar(String texto) in /home/ana/cs/relanca2.cs:line 15 at Program.<Main>$(String[] args) in /home/ana/cs/relanca2.cs:line 1
Compare os dois rastros. Com throw e;, o rastro recomeça na linha 15, a do próprio throw e;. O Ler e o int.Parse sumiram: quem for consertar o problema vai procurar no lugar errado. O compilador já avisou, na primeira linha: CA2200, "gerar novamente uma exceção detectada altera as informações de pilha". Os avisos CA… vêm dos analisadores de código do .NET, que procuram maus hábitos além dos erros da linguagem.
throw; | throw e; | |
|---|---|---|
| O que relança | A mesma exceção | A mesma exceção |
| Stack trace | Preservado: mostra onde ela nasceu | Recomeça no throw e; |
| Aviso | nenhum | CA2200 |
| Onde pode ser escrito | Só dentro de um catch (senão CS0156) | Em qualquer lugar |
| Use | Sempre que for relançar | Nunca para relançar |
O editor desta página roda os mesmos analisadores do dotnet: o aviso CA2200 e o rastro curto do throw e; saem iguais. No rastro do throw; há duas diferenças pequenas: aparecem mais linhas at System.… de dentro do int.Parse, e a linha do Carregar é a 15 (a do throw;), e não a 9 (a da chamada a Ler). O Ler e a linha 21 aparecem nos dois, que é o que importa.
5. Embrulhar: uma exceção dentro da outra
Há uma terceira saída: lançar uma exceção nova, com uma mensagem que faça sentido para quem chamou, guardando a original dentro dela. A original fica na propriedade InnerException ("exceção interna"), e nada se perde:
Estoque.Carregar("doze");
static class Estoque
{
public static void Carregar(string texto)
{
try
{
int quantidade = int.Parse(texto);
Console.WriteLine($"Quantidade: {quantidade}");
}
catch (FormatException e)
{
throw new InvalidOperationException($"Não deu para carregar o estoque: \"{texto}\".", e);
}
}
}
$ dotnet run embrulha.cs Unhandled exception. System.InvalidOperationException: Não deu para carregar o estoque: "doze". ---> System.FormatException: The input string 'doze' was not in a correct format. at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value) at System.Int32.Parse(String s) at Estoque.Carregar(String texto) in /home/ana/cs/embrulha.cs:line 9 --- End of inner exception stack trace --- at Estoque.Carregar(String texto) in /home/ana/cs/embrulha.cs:line 14 at Program.<Main>$(String[] args) in /home/ana/cs/embrulha.cs:line 1
- new InvalidOperationException(mensagem, e)
- O segundo argumento é a exceção original, que vira a
InnerExceptionda nova. - Unhandled exception. System.InvalidOperationException: Não deu…
- A exceção de fora, com a sua mensagem.
- ---> System.FormatException: …
- A seta
--->marca a exceção de dentro, com o rastro dela (linha 9, oint.Parse). - --- End of inner exception stack trace ---
- "Fim do rastro da exceção interna." Daqui para baixo, é o rastro da exceção de fora (linha 14, o
throw new).
Quem captura a exceção de fora pode ler as duas:
try
{
Estoque.Carregar("doze");
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
Console.WriteLine($"Causa: {e.InnerException?.GetType().Name}: {e.InnerException?.Message}");
}
$ dotnet run embrulha2.cs Não deu para carregar o estoque: "doze". Causa: FormatException: The input string 'doze' was not in a correct format.
O ?. está ali porque InnerException pode ser null: nem toda exceção tem uma "de dentro".
6. Expressões throw C# 7
Até aqui, throw foi uma instrução, numa linha própria. Desde o C# 7 ele também pode aparecer no meio de uma expressão, em quatro lugares: depois de ??, como um dos lados do ternário ? :, num braço de uma expressão switch e depois de =>. Todos de uma vez:
var ana = new Cliente("Ana", 29);
Console.WriteLine($"{ana.Nome}, {ana.Idade} anos");
Console.WriteLine(Categoria(2));
try { var x = new Cliente(null, 30); }
catch (ArgumentNullException e) { Console.WriteLine(e.Message); }
try { var y = new Cliente("Bia", -3); }
catch (ArgumentOutOfRangeException e) { Console.WriteLine(e.Message); }
Console.WriteLine(Categoria(9));
string Categoria(int codigo) => codigo switch
{
1 => "Alimentos",
2 => "Limpeza",
_ => throw new ArgumentOutOfRangeException(nameof(codigo), $"Categoria {codigo} não existe.")
};
class Cliente
{
public string Nome { get; }
public int Idade { get; }
public Cliente(string? nome, int idade)
{
Nome = nome ?? throw new ArgumentNullException(nameof(nome));
Idade = idade >= 0 ? idade : throw new ArgumentOutOfRangeException(nameof(idade), "A idade não pode ser negativa.");
}
public decimal CalcularFrete() => throw new NotImplementedException();
}
$ dotnet run expr.cs Ana, 29 anos Limpeza Value cannot be null. (Parameter 'nome') A idade não pode ser negativa. (Parameter 'idade') Unhandled exception. System.ArgumentOutOfRangeException: Categoria 9 não existe. (Parameter 'codigo') at Program.<<Main>$>g__Categoria|0_0(Int32 codigo) in /home/ana/cs/expr.cs:line 14 at Program.<Main>$(String[] args) in /home/ana/cs/expr.cs:line 8
- Nome = nome ?? throw new ArgumentNullException(nameof(nome));
- "Use
nome; se fornull, lance." O??(aula Operadores ??, ?. e ??=) só avalia o lado direito quando o esquerdo énull. É a forma mais comum de expressão throw, e de quebra o compilador entende queNomenunca ficanull. - idade >= 0 ? idade : throw new …
- No ternário: "se a idade não é negativa, use-a; senão, lance."
- _ => throw new …
- No
switch(aula Expressões switch): o braço_("qualquer outro valor") lança. Sem ele, o compilador avisa (CS8509: "a expressão switch não manipula todos os valores possíveis") e um código desconhecido lança umaSwitchExpressionExceptiongenérica (Non-exhaustive switch expression failed to match its input), sem a sua mensagem. - CalcularFrete() => throw new NotImplementedException();
- Um método ainda por fazer. Compila, e quem chamar descobre na hora que falta escrever.
Fora desses quatro lugares, o throw continua sendo uma instrução:
int x = throw new InvalidOperationException("não");
$ dotnet run tx.cs /home/ana/cs/tx.cs(1,9): error CS8115: Uma expressão throw não é permitida neste contexto. Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Missão: o Produto aceita nome null sem reclamar. Troque a linha Nome = nome; por uma expressão ?? throw que lance ArgumentNullException com nameof(nome).
var caderno = new Produto("Caderno");
Console.WriteLine(caderno.Nome);
try
{
var semNome = new Produto(null);
Console.WriteLine($"Criado: [{semNome.Nome}]");
}
catch (ArgumentNullException e)
{
Console.WriteLine(e.Message);
}
class Produto
{
public string? Nome { get; }
public Produto(string? nome)
{
Nome = nome;
}
}
var caderno = new Produto("Caderno");
Console.WriteLine(caderno.Nome);
try
{
var semNome = new Produto(null);
Console.WriteLine($"Criado: [{semNome.Nome}]");
}
catch (ArgumentNullException e)
{
Console.WriteLine(e.Message);
}
class Produto
{
public string? Nome { get; }
public Produto(string? nome)
{
Nome = nome ?? throw new ArgumentNullException(nameof(nome));
}
}
Formas diferentes, qual usar
| Situação | Forma |
|---|---|
| Parâmetro inválido, com mensagem sua | if (…) throw new ArgumentOutOfRangeException(nameof(x), x, "…"); |
Parâmetro null, negativo, vazio (mensagem padrão serve) | ArgumentNullException.ThrowIfNull(x);, ArgumentOutOfRangeException.ThrowIfNegative(n);… |
Guardar um parâmetro que não pode ser null | Campo = valor ?? throw new ArgumentNullException(nameof(valor)); |
Valor que não cabe em nenhum caso do switch | _ => throw new ArgumentOutOfRangeException(…) |
| Registrar e deixar subir | catch (…) { log; throw; } |
| Trocar por uma exceção com mais sentido para quem chama | throw new OutraException("…", e); (guarda a original em InnerException) |
| Método ainda não escrito | => throw new NotImplementedException(); |
| Problema próprio do seu programa | Uma exceção sua: Exceções Próprias |
Quando dá errado
throw "deu errado";
$ dotnet run erros.cs /home/ana/cs/erros.cs(1,7): error CS0029: Não é possível converter implicitamente tipo "string" em "System.Exception" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: só objetos da família Exception podem ser lançados. Um texto não é uma exceção (em JavaScript e em Python dá para lançar quase qualquer coisa; em C#, não).
Como consertar: embrulhe a mensagem numa exceção: throw new InvalidOperationException("deu errado");.
throw; fora de um catch
Console.WriteLine("oi");
throw;
$ dotnet run erros2.cs /home/ana/cs/erros2.cs(2,1): error CS0156: Uma instrução throw sem argumentos não é permitida fora de uma cláusula catch Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: throw; quer dizer "relance a exceção que estou tratando". Fora de um catch, não há exceção nenhuma para relançar.
Como consertar: fora do catch, escreva o que lançar: throw new …;.
O que significa: você escreveu throw e; num catch. O rastro da exceção recomeça ali, e a origem do problema some do relatório.
Como consertar: troque por throw;. Se a ideia era lançar uma exceção nova, passe a original como InnerException: throw new …("…", e);.
O que significa: o relatório diz (Parameter 'percentual'), mas o seu método chama o parâmetro de desconto. Alguém renomeou o parâmetro e esqueceu de trocar o texto escrito à mão.
Como consertar: use nameof(desconto) em vez de "desconto": o compilador acompanha a mudança de nome.
Referência
| Sintaxe | O que faz |
|---|---|
throw new Tipo("mensagem"); | Lança uma exceção nova |
new ArgumentException("msg", nameof(p)) | Mensagem primeiro, parâmetro depois |
new ArgumentNullException(nameof(p)) | Parâmetro primeiro (mensagem opcional depois) |
new ArgumentOutOfRangeException(nameof(p), p, "msg") | Parâmetro, valor recebido, mensagem |
ArgumentNullException.ThrowIfNull(x) | Lança se x for null .NET 6 |
ArgumentOutOfRangeException.ThrowIfNegative(n) | Lança se n < 0 .NET 8 |
throw; | Relança a exceção do catch, com o rastro intacto |
throw new Tipo("msg", e); | Lança uma nova, guardando e em InnerException |
x ?? throw new … | Expressão throw depois de ?? C# 7 |
cond ? valor : throw new … | Expressão throw no ternário |
_ => throw new … | Expressão throw num switch |
Metodo() => throw new NotImplementedException(); | Expressão throw depois de => |
CS0029 | Tentou lançar algo que não é Exception |
CS0156 | throw; fora de um catch |
CS8115 | Expressão throw fora dos quatro lugares permitidos |
CA2200 (aviso) | throw e; perde o rastro |
Exercícios
1. Lançar
Complete para lançar a exceção:
2. Relançar sem perder o rastro
Complete a última linha do catch:
3. O nome do parâmetro
Complete com o operador que transforma o nome do parâmetro em texto:
4. Um método Sacar(decimal valor) recebeu valor = -50. Qual exceção ele deve lançar?
5. Qual a diferença entre throw; e throw e; dentro de um catch (Exception e)?
6. Onde uma expressão throw não pode aparecer?
Missão final: o catch relança com throw e;, e o relatório não mostra mais o método Ler, onde o problema nasceu. Conserte o relançamento para o rastro voltar a mostrar Estoque.Ler.
Estoque.Carregar("doze");
static class Estoque
{
public static void Carregar(string texto)
{
try
{
int quantidade = Ler(texto);
Console.WriteLine($"Quantidade: {quantidade}");
}
catch (FormatException e)
{
Console.WriteLine($"[log] falhou ao ler \"{texto}\": {e.Message}");
throw e;
}
}
static int Ler(string texto)
{
return int.Parse(texto);
}
}
Estoque.Carregar("doze");
static class Estoque
{
public static void Carregar(string texto)
{
try
{
int quantidade = Ler(texto);
Console.WriteLine($"Quantidade: {quantidade}");
}
catch (FormatException e)
{
Console.WriteLine($"[log] falhou ao ler \"{texto}\": {e.Message}");
throw;
}
}
static int Ler(string texto)
{
return int.Parse(texto);
}
}
Resumo
throw new Tipo("mensagem");lança uma exceção: o método para ali e o problema vai para quem chamou, como as exceções do .NET.- Escolha uma exceção pronta:
ArgumentNullException,ArgumentOutOfRangeException,ArgumentExceptionpara parâmetros;InvalidOperationExceptionpara o estado do objeto. Nunca lanceException. - Use
nameof(parametro)para o nome do parâmetro. Os atalhosThrowIfNull,ThrowIfNegative… fazem oife othrownuma linha. - Para relançar num
catch,throw;.throw e;apaga a origem do rastro (avisoCA2200). - Para trocar por uma exceção mais clara, passe a original:
throw new …("…", e), e ela fica emInnerException. - Expressões throw:
?? throw,? … : throw,_ => throwe=> throw.