C# Programming Tutorial 0/45 lessons ~6 min read Lesson 28

    Exception Handling

    Exceptions signal exceptional failure — not normal control flow.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Exceptions signal exceptional failure — not normal control flow. try/catch/finally blocks handle errors; unhandled exceptions crash the process or return HTTP 500. ASP.NET Core middleware converts exceptions to ProblemDetails responses.

    Specific catch order matters — most specific first. finally runs for cleanup regardless. Interviewers ask exception vs Result pattern, when not to catch, and performance cost of throw.

    The story

    A payment microservice calls an external charge API over HTTP. Network glitches, invalid requests, and user cancellations all produce different failures — the client wraps low-level HTTP errors into domain-friendly exceptions while preserving the original error for logs and re-throwing cooperative cancellations unchanged.

    Understanding the topic

    Key concepts

    • try contains guarded code; catch handles types; finally always runs.
    • Exception base class; specialized types ArgumentException, InvalidOperationException.
    • throw; rethrows preserving stack trace.
    • throw ex; resets stack — avoid.
    • Filter catch when (condition) (.NET 6+).
    • Global handlers: AppDomain.UnhandledException, ASP.NET exception middleware.
    text
    flowchart TD
    Try[try block] --> Error{Exception?}
    Error -->|yes| Catch[catch handler]
    Error -->|no| Finally[finally block]
    Catch --> Finally
    Finally --> Continue[Resume or Rethrow]

    Step-by-step explanation

    1. Runtime walks stack seeking matching catch.
    2. catch (Exception ex) logs and wraps or rethrows.
    3. finally disposes resources — prefer using statement.
    4. Async exceptions captured in Task — await propagates.
    5. AggregateException wraps parallel task failures.
    6. Exception filters evaluate before stack unwind.

    Practical code example

    Safe HTTP call with specific catches and exception filter:

    csharp
    namespace TechLearningPro.Exceptions;
    public sealed class PaymentClient(HttpClient http)
    {
    public async Task<string> ChargeAsync(decimal amount, CancellationToken ct)
    {
    try
    {
    var response = await http.PostAsync(
    quot;/charge?amount={amount}", null, ct);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync(ct);
    }
    catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.BadRequest)
    {
    throw new InvalidOperationException("Invalid charge request.", ex);
    }
    catch (TaskCanceledException) when (ct.IsCancellationRequested)
    {
    throw; // cooperative cancel
    }
    }
    }

    Line-by-line code explanation

    • PaymentClient(HttpClient http) receives an injected HTTP client from IHttpClientFactory.
    • try { ... } wraps the network call where failures are expected occasionally.
    • await http.PostAsync(...) sends the charge request asynchronously.
    • response.EnsureSuccessStatusCode() throws if the HTTP status indicates failure.
    • return await response.Content.ReadAsStringAsync(ct) reads the success body on the happy path.
    • catch (HttpRequestException ex) when (ex.StatusCode == BadRequest) catches only 400 errors via an exception filter.
    • throw new InvalidOperationException("Invalid charge request.", ex) wraps the error with context and inner exception.
    • catch (TaskCanceledException) when (ct.IsCancellationRequested) distinguishes user cancel from timeout.
    • throw; rethrows without resetting the stack trace — correct for cooperative cancellation.

    Key takeaway: Wrap low-level exceptions in domain-friendly types. when filter distinguishes timeout vs cancel. Preserve inner exception for diagnostics.

    Real-world use

    Where you'll use this in production

    • API clients translating HTTP errors.
    • Database retry on transient SqlException.
    • Validation throwing ArgumentException on bad input.
    • Global exception middleware logging correlation IDs.

    Best practices

    • Catch specific types; avoid bare catch unless top-level.
    • Use using/await using for deterministic dispose.
    • Log exception + context; never swallow silently.
    • Prefer Try pattern for expected failures.
    • Map exceptions to HTTP status in API layer only.

    Common mistakes

    • catch (Exception) { } empty swallow.
    • throw ex; losing stack trace.
    • Using exceptions for validation flow control.
    • Catching ThreadAbortException (legacy).

    Advanced interview questions

    Q1Beginnerfinally without catch?
    Valid — try/finally for cleanup only.
    Q2Beginnerusing statement purpose?
    Compile-time try/finally calling Dispose.
    Q3IntermediateException vs Result type?
    Exception unexpected; Result expected failure without stack cost.
    Q4IntermediateAsync exception propagation?
    Stored in Task; surfaces on await; use AggregateException in WaitAll.
    Q5AdvancedDesign global error handling for ASP.NET Core API.
    Exception middleware → ProblemDetails + traceId; map ValidationException 400; log 500; never leak stack to clients.

    Summary

    try/catch/finally handles exceptional failures. Catch specific types; preserve stack with throw; using and await using manage disposable resources. Map exceptions to HTTP at API boundary. Next: custom exception types.

    Ready to mark this lesson complete?Track your journey across the entire course.