Lessons in this course (9)

1. try, catch, finally, filters and rethrowing correctly

Basic★ Must knowLesson 1 of 9
csharp
public async Task<Report> LoadReportAsync(string path, CancellationToken ct)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(path);                    // guard clause (.NET 8)
    FileStream? stream = null;
    try
    {
        stream = File.OpenRead(path);
        return await ReportParser.ParseAsync(stream, ct);
    }
    catch (FileNotFoundException ex)                                   // most specific first
    {
        throw new ReportNotFoundException(path, ex);                    // wrap with context, keep inner exception
    }
    catch (JsonException ex) when (ex.LineNumber is not null)           // exception filter — no unwinding if false
    {
        logger.LogWarning(ex, "Bad report format at line {Line}", ex.LineNumber);
        throw;                                                          // rethrow, keeps original stack trace
    }
    catch (OperationCanceledException) when (ct.IsCancellationRequested)
    {
        throw;                                                          // cancellation is not an error — let it flow
    }
    finally
    {
        stream?.Dispose();                                              // always runs (prefer 'using')
    }
}
RuleWhy
Catch specific exceptionsYou know how to handle them; others should bubble
throw; not throw ex;throw ex resets the stack trace
Wrap with an inner exceptionAdds context without losing the cause
using instead of finally for disposalShorter, can't forget
Never catch { } emptyHidden failures, impossible debugging
Don't catch Exception in the middle of the codeOnly at boundaries (global handler, background loop)
Exceptions are expensiveDon't use them for normal flow (TryParse, TryGetValue)

ExceptionDispatchInfo.Capture(ex).Throw() rethrows an exception captured elsewhere (another thread) while preserving its stack trace.