C#: Tratamento de exceções
1. Mecanismo básico do try/catch/finally
O C# utiliza três palavras-chave — try, catch e finally — para construir seu mecanismo de tratamento de exceções. O código que pode lançar uma exceção é colocado no bloco try; o bloco catch captura e trata a exceção; e o bloco finally é sempre executado, independentemente de ter ocorrido uma exceção, sendo comumente usado para a limpeza de recursos.
try
{
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Caught exception: {ex.Message}");
}
finally
{
Console.WriteLine("finally block always executes");
}
Caught exception: Attempted to divide by zero.
finally block always executes
(1) Blocos de captura múltipla
É possível interceptar diferentes tipos de exceções em ordem. Os tipos de exceção mais específicos devem ser colocados antes dos mais gerais.
try
{
int[] arr = { 1, 2, 3 };
Console.WriteLine(arr[10]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"Index out of bounds: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"General exception: {ex.Message}");
}
Index out of bounds: Index was outside the bounds of the array.
2. Tipos comuns de exceção
| Tipo de exceção | Cenário de acionamento |
|---|---|
FormatException |
Formato de string inválido, por exemplo, int.Parse("abc") |
NullReferenceException |
Acessando um membro de um objeto null |
IndexOutOfRangeException |
Índice da matriz fora do intervalo |
DivideByZeroException |
Número inteiro dividido por zero |
OverflowException |
Estouro aritmético (em contexto verificado) |
FileNotFoundException |
O arquivo não existe |
ArgumentException |
Argumento de método inválido |
▶ Exemplo
try
{
int num = int.Parse("hello");
}
catch (FormatException)
{
Console.WriteLine("Format exception: string cannot be converted to integer");
}
string s = null;
try
{
Console.WriteLine(s.Length);
}
catch (NullReferenceException)
{
Console.WriteLine("Null reference exception: object is null");
}
Format exception: string cannot be converted to integer
Null reference exception: object is null
3. Hierarquia das classes de exceção
A relação de herança entre todas as classes de exceção é a seguinte:
Object
└─ Exception
└─ SystemException
├─ FormatException
├─ NullReferenceException
├─ IndexOutOfRangeException
├─ DivideByZeroException
├─ OverflowException
├─ FileNotFoundException
└─ ArgumentException
└─ ArgumentNullException
4. Propriedades da classe de exceção
A classe Exception oferece as seguintes propriedades comumente utilizadas para obter informações detalhadas sobre exceções:
| Propriedade | Descrição |
|---|---|
Message |
Texto legível que descreve a exceção |
StackTrace |
Informações da pilha de chamadas no momento em que a exceção ocorreu |
InnerException |
A exceção interna que causou a exceção atual |
Source |
Nome do aplicativo ou objeto que lançou a exceção |
TargetSite |
O método que lançou a exceção |
▶ Exemplo
try
{
int zero = 0;
int result = 100 / zero;
}
catch (Exception ex)
{
Console.WriteLine($"Message: {ex.Message}");
Console.WriteLine($"Source: {ex.Source}");
Console.WriteLine($"TargetSite: {ex.TargetSite}");
}
Message: Attempted to divide by zero.
Source: ConsoleApp
TargetSite: Void Main()
5. Lançamento de exceções com throw
Use a palavra-chave throw para lançar ativamente uma exceção. Ao relançar uma exceção, throw; preserva o rastreamento de pilha original, enquanto throw ex; reinicia a pilha. Evite usar esta última.
▶ Exemplo
void CheckAge(int age)
{
if (age < 0)
{
throw new ArgumentException("Age cannot be negative", nameof(age));
}
Console.WriteLine($"Age: {age}");
}
try
{
CheckAge(-5);
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
Age cannot be negative (Parameter 'age')
(1) Diferença entre throw e throw ex
void InnerMethod()
{
throw new InvalidOperationException("Internal error");
}
void OuterMethod()
{
try
{
InnerMethod();
}
catch (Exception ex)
{
throw;
}
}
try
{
OuterMethod();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
at InnerMethod() in Program.cs:line 2
at OuterMethod() in Program.cs:line 10
throw; para relançar exceções. Isso preserva a pilha de chamadas completa, facilitando a localização da causa raiz. throw ex; trunca a pilha até o método atual, perdendo as informações originais sobre a localização da exceção.
6. Filtros de exceção
O C# 6 introduziu filtros de exceção com a cláusula when, permitindo que condições sejam adicionadas a catch. A exceção só é capturada quando a condição resulta em true.
▶ Exemplo
try
{
throw new HttpRequestException("Network timeout, please retry later");
}
catch (HttpRequestException ex) when (ex.Message.Contains("timeout"))
{
Console.WriteLine("Caught timeout exception, preparing to retry");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Other network exception: {ex.Message}");
}
Caught timeout exception, preparing to retry
7. Classes de exceção personalizadas
Crie classes de exceção personalizadas herdando de Exception. Normalmente, é necessário implementar três construtores: um construtor sem parâmetros, um construtor com um parâmetro de mensagem e um construtor com parâmetros de mensagem e de exceção interna.
▶ Exemplo
class InsufficientBalanceException : Exception
{
public decimal Balance { get; }
public decimal Amount { get; }
public InsufficientBalanceException()
: base("Insufficient balance") { }
public InsufficientBalanceException(string message)
: base(message) { }
public InsufficientBalanceException(string message, Exception innerException)
: base(message, innerException) { }
public InsufficientBalanceException(decimal balance, decimal amount)
: base($"Insufficient balance: current balance {balance}, required {amount}")
{
Balance = balance;
Amount = amount;
}
}
class BankAccount
{
public decimal Balance { get; private set; }
public BankAccount(decimal balance)
{
Balance = balance;
}
public void Withdraw(decimal amount)
{
if (amount > Balance)
{
throw new InsufficientBalanceException(Balance, amount);
}
Balance -= amount;
Console.WriteLine($"Withdrawal successful, balance: {Balance}");
}
}
var account = new BankAccount(100);
try
{
account.Withdraw(200);
}
catch (InsufficientBalanceException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"Balance: {ex.Balance}, attempted withdrawal: {ex.Amount}");
}
Insufficient balance: current balance 100, required 200
Balance: 100, attempted withdrawal: 200
8. Melhores práticas para tratamento de exceções
(1) Intercepte exceções específicas, não apenas exceções em geral
try
{
int value = int.Parse(input);
Console.WriteLine(100 / value);
}
catch (FormatException)
{
Console.WriteLine("Input is not a valid integer");
}
catch (DivideByZeroException)
{
Console.WriteLine("Cannot input zero");
}
catch vazios para suprimir exceções, pois isso dificulta o diagnóstico de problemas.
(2) Não abafar exceções
try
{
File.WriteAllText("data.txt", content);
}
catch (Exception)
{
}
O código acima oculta todos os erros e é uma prática inadequada. No mínimo, você deve registrar o erro no log ou re-lançá-lo.
(3) Use o finally para liberar recursos
FileStream fs = null;
try
{
fs = new FileStream("data.txt", FileMode.Open);
int b = fs.ReadByte();
}
finally
{
fs?.Dispose();
}
(4) Use a instrução using em vez de try-finally
Os recursos que implementam a interface IDisposable devem dar preferência à instrução using, que chama automaticamente Dispose no final do escopo, garantindo a liberação do recurso mesmo que ocorra uma exceção.
using (var fs = new FileStream("data.txt", FileMode.Open))
{
int b = fs.ReadByte();
}
using é equivalente à try-finally com uma chamada à Dispose() e é a abordagem preferida para o gerenciamento de recursos.
(5) Envolver exceções com InnerException
try
{
SaveToFile();
}
catch (IOException ioEx)
{
throw new ApplicationException("Failed to save data", ioEx);
}
Ao usar InnerException, as informações originais da exceção são preservadas sem perda de contexto.
❓ Perguntas Frequentes
P: Qual é a diferença entre
throw;ethrow ex;? R:throw;preserva o rastreamento de pilha original;throw ex;reinicializa a pilha para o método atual. Usethrow;ao depurar.
P: Quando o bloco
finallynão será executado? R: Quando o programa for encerrado à força (por exemplo,Environment.FailFast) ou ocorrer umStackOverflowException, ofinallypode não ser executado.
P: É possível ter
try-finallysemcatch? R: Sim. Essa abordagem não lida com a exceção, mas garante a liberação dos recursos; a exceção continua se propagando para os níveis superiores.
P: As classes de exceção personalizadas devem herdar de
Exception? R: Sim, todas as classes de exceção devem herdar deException(direta ou indiretamente) para que possam ser capturadas porcatch.
P: Quais são as vantagens do filtro de exceção
when? R: A cláusulawhennão captura a exceção quando a condição não é atendida, permitindo que a exceção continue se propagando. Isso é mais eficiente e semanticamente mais claro do que verificar condições dentro decatch.
📖 Resumo
- O bloco
tryenvolve código que pode gerar erros,catchcaptura exceções efinallysempre é executado - Os tipos de exceção mais comuns incluem
FormatException,NullReferenceException,IndexOutOfRangeException,DivideByZeroException, etc. - A classe
Exceptiondisponibiliza as propriedadesMessage,StackTrace,InnerExceptione outras throw;preserva a pilha;throw ex;reinicializa a pilha — use a primeira opção- As classes de exceção personalizadas herdam de
Exceptione implementam construtores padrão - O filtro de exceções
catch ... when (...)oferece a capacidade de captura condicional - Não ignore exceções, capture tipos específicos, use
usingpara gerenciamento de recursos, envolva exceções comInnerException
📝 Exercícios
- Escreva um método
int SafeParse(string s)que utilizetry-catchpara processarFormatException, retornando0e exibindo um aviso caso a análise falhe - Crie uma exceção personalizada
InvalidGradeExceptioncom uma propriedadeGradeque seja lançada quando a nota não estiver no intervalo de 0 a 100 - Escreva um código que demonstre a diferença entre
throw;ethrow ex;, exibindo os valores deStackTracee observando a diferença - Use o filtro de exceção
whenpara implementar: captureFileNotFoundExceptionsomente quando o caminho do arquivo começar com"config"; caso contrário, deixe a exceção se propagar - Escreva um método de leitura de arquivo que utilize a instrução
usingpara garantir queStreamReaderseja liberado corretamente, lidando com possíveisFileNotFoundExceptioneIOException