Módulo 1 · C# — Capítulo 08

Tratamento de Exceções

try/catch/finally em profundidade, exceções customizadas e por que "engolir" uma exceção é quase sempre um erro.

1. Anatomia completa: try/catch/finally

C# os três blocos
try
{
    var resultado = 10 / int.Parse("0");
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Divisão por zero: {ex.Message}");
}
catch (FormatException ex)
{
    Console.WriteLine($"Formato inválido: {ex.Message}");
}
finally
{
    Console.WriteLine("Sempre executa, com ou sem exceção");
}
BlocoQuando executa
trySempre, até o ponto onde uma exceção ocorre (se ocorrer)
catchSó se uma exceção do tipo declarado (ou subtipo) foi lançada
finallySempre — com exceção capturada, sem capturar, ou até com return no meio do try
Atenção — ordem dos catch importa

Blocos catch são avaliados de cima para baixo, e o primeiro que corresponde ao tipo da exceção (ou a um tipo base dela) é usado. Um catch (Exception ex) genérico colocado antes de um catch mais específico faz o específico nunca ser alcançado — e o compilador acusa erro exatamente por isso.

2. A hierarquia de exceções

Toda exceção em C# deriva, direta ou indiretamente, de System.Exception. Capturar pelo tipo mais específico possível (em vez de sempre Exception) deixa claro qual falha você realmente sabe tratar:

Exception ArgumentException InvalidOperationException SqlException ArgumentNullException

Fig. 1 — Capturar ArgumentNullException também captura via catch (ArgumentException), mas não o contrário.

3. Exceções customizadas

Para erros específicos do seu domínio, crie uma classe de exceção própria — é exatamente o padrão que fechou o capítulo 13 do Módulo 2 (SQL), quando um SqlException com número 50010 virava uma EstoqueInsuficienteException:

C# exceção customizada
public class EstoqueInsuficienteException : Exception
{
    public int ProdutoId { get; }

    public EstoqueInsuficienteException(int produtoId, string mensagem)
        : base(mensagem)
    {
        ProdutoId = produtoId;
    }
}

// lançando
if (produto.Estoque < quantidadeDesejada)
    throw new EstoqueInsuficienteException(produto.Id, $"Estoque insuficiente para o produto {produto.Id}");

// capturando especificamente
try
{
    FinalizarCompra(pedido);
}
catch (EstoqueInsuficienteException ex)
{
    Console.WriteLine($"Produto {ex.ProdutoId} sem estoque suficiente");
}

4. Exception filters com when

Você já viu isso no Módulo 2: when adiciona uma condição extra ao catch, capturando só quando essa condição também for verdadeira.

C# catch com filtro
try
{
    ExecutarProcedure();
}
catch (SqlException ex) when (ex.Number == 50010)
{
    Console.WriteLine("Estoque insuficiente");
}
catch (SqlException ex) when (ex.Number == 50001)
{
    Console.WriteLine("Valor de transferência inválido");
}
catch (SqlException ex)
{
    Console.WriteLine($"Outro erro de banco: {ex.Number}");
}

5. Relançando uma exceção corretamente

C# throw; vs. throw ex;
try
{
    OperacaoArriscada();
}
catch (Exception ex)
{
    LogarErro(ex);
    throw;      // correto — preserva o stack trace original
    // throw ex; // ERRADO — reseta o stack trace, escondendo onde o erro realmente ocorreu
}
O paralelo com T-SQL

Isso é exatamente o mesmo cuidado do THROW; sem argumentos que você viu no capítulo 6 do Módulo 2 — em ambas as linguagens, relançar "do jeito errado" reconstrói o erro e perde informação de onde ele realmente aconteceu.

6. Não engula exceções

C# o anti-padrão mais perigoso
try
{
    ProcessarPagamento(pedido);
}
catch (Exception)
{
    // vazio — o erro desaparece silenciosamente
}
Por que isso é grave

Um catch vazio faz a falha desaparecer sem rastro — o pagamento não foi processado, ninguém sabe, e o sistema segue como se nada tivesse acontecido. Se realmente não há nada a fazer com um erro específico, ao menos registre-o (log) antes de decidir seguir em frente.

7. Onde isso te leva

Com tratamento de erro sólido, o próximo capítulo muda de assunto para LINQ — a ferramenta que você vai usar praticamente todos os dias para consultar coleções em memória (e, mais adiante, também o banco via Entity Framework).

📌 Resumo do capítulo

  • finally sempre executa, mesmo com return dentro do try.
  • Ordene blocos catch do mais específico para o mais genérico.
  • Exceções customizadas (herdando de Exception) tornam o tratamento de erro de domínio explícito e tipado.
  • when filtra qual catch se aplica com base numa condição extra.
  • Use throw; (sem argumento) para relançar sem perder o stack trace original; nunca deixe um catch vazio.

✏️ Praticando

  1. Crie a exceção SaldoInsuficienteException e use-a no método Sacar da classe ContaBancaria (capítulo 5).
  2. Escreva um bloco try/catch com três catches diferentes (um específico, um com when, um genérico) para um método que pode lançar FormatException, SaldoInsuficienteException ou qualquer outra exceção.
  3. Identifique, no seu próprio código de outros projetos, algum catch vazio ou throw ex; — e corrija.