Roadmap › Exception Handling & Custom Error Handling in .NET — Every Way, Every App Type › Module: Exceptions in C#
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')
}
}| Rule | Why |
|---|---|
| Catch specific exceptions | You know how to handle them; others should bubble |
throw; not throw ex; | throw ex resets the stack trace |
| Wrap with an inner exception | Adds context without losing the cause |
using instead of finally for disposal | Shorter, can't forget |
Never catch { } empty | Hidden failures, impossible debugging |
Don't catch Exception in the middle of the code | Only at boundaries (global handler, background loop) |
| Exceptions are expensive | Don't use them for normal flow (TryParse, TryGetValue) |
ExceptionDispatchInfo.Capture(ex).Throw() rethrows an exception captured elsewhere (another thread) while preserving its stack trace.