Reading — step 1 of 7
Learn
C# exceptions follow the Java/CLR model — try/catch/finally with typed exception classes. The Microsoft docs cover this thoroughly; this lesson hits the patterns you'll use daily.
try / catch / finally
try
{
var data = File.ReadAllText("config.json");
var config = JsonSerializer.Deserialize<Config>(data);
}
catch (FileNotFoundException ex)
{
Console.Error.WriteLine($"config missing: {ex.Message}");
return DefaultConfig;
}
catch (JsonException ex)
{
Console.Error.WriteLine($"bad json: {ex.Message}");
throw; // re-throw
}
catch (Exception ex)
{
Console.Error.WriteLine($"unexpected: {ex.Message}");
throw;
}
finally
{
Cleanup(); // always runs
}
- Multiple
catchclauses — most-specific first finallyruns regardless — success, caught exception, uncaught exception- Bare
throwre-raises preserving the original stack trace throw exresets the stack trace (almost always wrong)
Throwing exceptions
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be empty", nameof(name));
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age), age, "age must be non-negative");
nameof(name) produces the string "name" — type-safe, refactor-friendly.
Common BCL exceptions:
ArgumentException,ArgumentNullException,ArgumentOutOfRangeException— bad parametersInvalidOperationException— object in wrong state for the callNotSupportedException— operation not supported by this implementationNotImplementedException— placeholder during developmentIOException,FileNotFoundException— I/O errors
Custom exceptions
public class ValidationException : Exception
{
public string Field { get; }
public ValidationException(string field, string message) : base(message)
{
Field = field;
}
public ValidationException(string field, string message, Exception inner)
: base(message, inner)
{
Field = field;
}
}
throw new ValidationException("email", "must contain @");
Convention: name ends in Exception. Always provide a string-message constructor AND a string-message + inner-exception constructor (for wrapping). Inherit from Exception.
Filter clauses (C# 6+)
try
{
DoWork();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
HandleNotFound();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
HandleAuth();
}
The when clause is evaluated WITHOUT unwinding the stack. Different from rethrowing in the body — preserves the exception's original location for debugging.
using statement — auto-dispose
using (var stream = File.OpenRead("data.bin"))
{
// use stream
} // stream.Dispose() called automatically, even if exception
// C# 8+ using declaration:
using var stream = File.OpenRead("data.bin");
// disposes when the enclosing scope ends
Any type implementing IDisposable can be used. Replaces the verbose try/finally for cleanup.
try / async
try
{
var result = await SomeAsyncOperation();
}
catch (OperationCanceledException)
{
Console.WriteLine("cancelled");
}
catch (Exception ex)
{
Console.WriteLine($"failed: {ex.Message}");
}
Exception handling works seamlessly with async/await. The compiler unwraps AggregateException for awaited tasks, so you catch the inner exception directly.
Common mistakes
- Catching
Exceptionindiscriminately — hides bugs. Catch specific types when you can react meaningfully. - Empty catch blocks — silent swallowing of errors. At minimum, log.
throw ex;instead ofthrow;— resets the stack trace. Use barethrowto preserve it.- Throwing string messages — must throw an exception object.
throw "oops";is a compile error. - Using exceptions for control flow — exceptions are slow and obscure intent. Use Try patterns (
int.TryParse) for expected failures. - Forgetting
usingfor IDisposables — leaks file handles, network connections, etc.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…