Exception Handling

Course 1 · Ch 7
Exception Handling
Every C# exception is unchecked — a deliberate, documented rejection of java1-7's checked/unchecked split

java1-7 positioned Java's checked exceptions as a real middle ground between C++'s unenforced exceptions and Rust's Result<T, E>. C#'s designers looked at that same middle ground and deliberately rejected it — this chapter explains why, with their own stated reasoning.

try/catch/finally — Same Shape

try { int result = 10 / 0; } catch (DivideByZeroException e) { Console.WriteLine(e.Message); } finally { Console.WriteLine("Always runs"); }

Structurally identical to java1-7try, a typed catch, an unconditional finally. The real divergence isn't the syntax.

Every C# Exception Is Unchecked

C# has no checked-exception concept at all. Every exception, without exception, behaves the way java1-7 described for RuntimeException — a method can throw anything, with zero compiler-enforced acknowledgment, ever. No throws clause exists in C#'s syntax to require in the first place.

Why C# Rejected Checked Exceptions

This was a deliberate design decision, not an oversight — Anders Hejlsberg (csharp1-1's own origin-story architect) has spoken publicly about the specific reasoning. Two real, documented critiques of java1-7's own model: checked exceptions don't version well — adding one new checked exception to a widely-used method's signature breaks every existing caller that doesn't already handle it, a real practical cost in library evolution. And in practice, checked exceptions get "handled" via empty catch blocks or wrapped indiscriminately in a generic RuntimeException just to satisfy the compiler — arguably making real code worse, not safer, than leaving the choice to the programmer's judgment.

XML Doc Comments as the Real (Weaker) Substitute

/// <summary>Withdraws funds from the account.</summary> /// <exception cref="InsufficientFundsException">Thrown when balance is too low.</exception> public void Withdraw(decimal amount) { /* ... */ }

C# lets an API author document what a method might throw via an XML doc comment's <exception> tag, surfaced in IntelliSense — but nothing here is compiler-enforced at all. It's purely informational, a real, direct contrast against java1-7's own compiler-enforced throws clause.

Exception Filters — A Genuinely Unique Addition

try { ProcessOrder(); } catch (HttpRequestException e) when (e.StatusCode == 503) { Retry(); // only fires when BOTH the type AND the condition match } catch (HttpRequestException e) { LogAndFail(e); // every other HttpRequestException falls here }

catch (...) when (condition) is a real feature with no direct Java equivalent — a catch block only fires when its exception type and a runtime condition both hold, avoiding a nested if statement (and a re-throw of everything that doesn't match) inside a broader catch.

using — C#'s Answer to try-with-resources

using var reader = new StreamReader(path); // disposed automatically at the END of the enclosing scope Console.WriteLine(reader.ReadLine()); // no closing brace needed — this is C# 8's using DECLARATION, more concise than java1-7's try(...)

Any type implementing IDisposable works with using, calling Dispose() automatically — genuinely comparable to java1-7's try-with-resources/AutoCloseable, and to cpp1-5's RAII. C# 8's using declaration form (shown above, no extra braces) is even more concise syntactically than Java's own block-scoped try(...) syntax.

AspectJava (java1-7)C#
Checked exceptionsyes — compiler-enforced catch or throwsno such concept exists
Documenting what a method might throwthrows clause — compiler-enforcedXML doc <exception> tag — informational only
Conditional catchnot built in — needs a nested ifcatch (...) when (condition) — built in
Automatic resource cleanuptry-with-resourcesusing — including a more concise declaration form
Reach for exception filters instead of re-throwing inside a broad catch
catch (...) when (condition) keeps a narrow, conditional handling case cleanly separate from the general case, without needing an inner if and a manual re-throw to fall through to a second catch block.
Nothing forces you to consider a realistic failure mode
Because C# has no checked-exception mechanism, the compiler never prompts a caller to think about what a method might throw — java1-7's own compiler-enforced acknowledgment simply has no C# counterpart. Knowing what a method can throw relies entirely on documentation, testing, and discipline, not language enforcement.

Coding Challenges

Challenge 1

Write a method that divides by zero inside a try block, catch DivideByZeroException specifically, and use a finally block that always runs. Then write a second method that throws a custom exception with no throws-style declaration anywhere, showing it compiles without any compiler warning at all.

📄 View solution
Challenge 2

Write a method that throws a custom exception with an int ErrorCode property, then use two catch blocks: one with a when filter that only fires for a specific error code, and a second, unfiltered catch for every other case.

📄 View solution
Challenge 3

Write a class implementing IDisposable with a Dispose() method that prints a message, then use it with a using declaration (not a using block) alongside code that throws an exception afterward in the same scope, showing Dispose() still runs.

📄 View solution

Chapter 7 Quick Reference

  • C# has no checked-exception concept — every exception behaves like java1-7's own RuntimeException, with zero compiler-enforced acknowledgment
  • This was a deliberate, documented design choice (Anders Hejlsberg) — checked exceptions version poorly and get swallowed via empty catches/blanket rethrows in practice
  • XML doc <exception> tags document possible throws for IntelliSense, but enforce nothing at compile time, unlike Java's throws clause
  • catch (...) when (condition) — exception filters with no direct Java equivalent
  • using (including C# 8's more concise using declaration) is C#'s IDisposable-based answer to try-with-resources
  • Next chapter: collections and a first taste of LINQ, previewing Course 2's own deep dive