Capítulo 14: Tratamento de Exceções, Filtros ‘when’ e o Padrão IDisposable
Capítulo 14: Tratamento de Exceções, Filtros ‘when’ e o Padrão IDisposable
Especialização em Sistemas com C# • C# 12 & .NET 8 • POO Moderna, LINQ, Concorrência e Arquitetura Corporativa
🗺️ Mapa Conceitual do Ciclo de Vida de Recursos e Gestão de Exceções
sequenceDiagram
autonumber
participant App as Código Chamador (using scope)
participant Recurso as Objeto IDisposable
participant OS as Sistema Operacional / Driver
App->>Recurso: new Recurso(handle)
activate Recurso
Recurso->>OS: Adquire Handle Nativo (Arquivo, Socket, HW)
App->>Recurso: Executa Operação de Risco
Note over App,Recurso: Disparo de Exceção OU Execução Nominal
App->>Recurso: Fim do Bloco using (Finally Implícito)
Recurso->>OS: Dispose() -> Libera Handle do SO Imediatamente
deactivate Recurso
Note over OS: Zero Vazamentos de Recursos Não Gerenciados
🏛️ 1. A Filosofia de Exceções no .NET: Robustez Sem Abusos de Fluxo
Em C#, as exceções devem ser reservadas exclusivamente para condições excepcionais — situações inesperadas em que um método não consegue cumprir seu contrato formal (queda de banco de dados, corrupção de arquivo, timeout de socket).
Princípio Fundamental de Performance:
Nunca utilize exceções para controle de fluxo de negócio normal (como validar se um usuário digitou um e-mail válido). Lançar uma exceção exige que a CLR capture o estado dos registradores da CPU, percorra a tabela de símbolos de debug para montar o Stack Trace e instancie múltiplos objetos no Heap.
Para fluxos previsíveis, adote o padrão Tester-Doer (int.TryParse em vez de int.Parse dentro de try/catch).
⚙️ 2. Filtros de Exceção com a Cláusula when
Um dos recursos mais poderosos e únicos do C# (suportado nativamente na CLR mas ausente no Java) são os Filtros de Exceção (when):
try
{
await GatewayPagamento.ProcessarAsync(cobranca);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.GatewayTimeout)
{
// Captura SOMENTE se a condição lógica for estritamente verdadeira!
Log.Warning("Timeout no gateway. Agendando retry assíncrono...");
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
Log.Error("Credenciais de API expiradas no provedor.");
}
A Vantagem de Arquitetura do when:
Ao contrário de capturar a exceção e relançá-la (throw;) dentro de um if, o filtro when é avaliado antes de a pilha ser desenrolada (Stack Unwinding). Isso significa que, se a condição for falsa, o processo de inspeção continua sem perder o contexto original de registradores da CPU, preservando a rastreabilidade exata do ponto original do erro em ferramentas de monitoramento como Application Insights.
📦 3. O Padrão IDisposable e a Declaração Moderna using
Como o Garbage Collector gerencia apenas memória RAM gerenciada, recursos nativos do sistema operacional (descritores de arquivos, conexões de banco de dados TCP, handles Win32) exigem liberação determinística explícita.
A Declaração Moderna using (C# 8+):
Em vez de aninhar múltiplos blocos com chaves using (var r = ...) { }, a sintaxe moderna associa o ciclo de vida da variável ao escopo léxico local atual:
void ProcessarArquivo()
{
// O compilador insere um bloco try/finally que chama Dispose() ao fim do método!
using var stream = new FileStream("dados.bin", FileMode.Open);
// Operações normais...
} // stream.Dispose() é chamado aqui incondicionalmente!
Para streams e bancos assíncronos, utilize await using implementando a interface IAsyncDisposable.
📊 4. Matriz Comparativa: throw vs throw ex vs when
| Sintaxe | Efeito no Stack Trace | Recomendação Arquitetural |
|---|---|---|
throw; |
Preserva 100% do Stack Trace original | Padrão Ouro para relançar exceções |
throw ex; |
Destrói o Stack Trace, resetando a origem para a linha atual | ANTI-PATTERN GRAVE (Proibido) |
catch (...) when (cond) |
Avalia condição sem desenrolar pilha | Máxima eficiência e rastreabilidade |
ExceptionDispatchInfo.Capture |
Salva e restaura exceção preservando trace | Ideal para pipelines de retry e tasks |
🔬 5. Laboratório de Arquitetura: Implementação do Padrão Dispose Canônico
Para classes que combinam recursos gerenciados (como outros objetos descartáveis) e recursos não gerenciados brutos (como ponteiros de memória nativa IntPtr), a Microsoft define o Padrão Dispose Canônico:
public class GerenciadorHardwareNativo : IDisposable
{
private bool _disposed;
private IntPtr _bufferNativo; // Recurso não gerenciado
public void Dispose()
{
Dispose(disposing: true);
// Informa ao Garbage Collector que o finalizador não precisa ser executado!
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Libera recursos gerenciados (outros objetos IDisposable)
}
// Libera recursos NÃO gerenciados (memória nativa / handles do SO)
if (_bufferNativo != IntPtr.Zero)
{
Marshal.FreeHGlobal(_bufferNativo);
_bufferNativo = IntPtr.Zero;
}
_disposed = true;
}
}
// Finalizador (executado pela thread Finalizer do GC se o programador esquecer o Dispose)
~GerenciadorHardwareNativo() => Dispose(disposing: false);
}