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);
}