BuildQuill
C# learning path
18 of 25
Lesson 18 of 25
Lesson 18Beginner20 min

C# Exceptions and Resource Cleanup

Handle failures at the right boundary, preserve useful diagnostics, and guarantee cleanup for files and other disposable resources.

Before this lesson

  • Validation
  • Methods
  • Classes and object contracts
  • Debugging

Separate expected alternative outcomes from exceptional failures

Use try, catch, finally, throw, and using deliberately

Avoid swallowing exceptions or exposing internal details

The short answer

An exception reports that an operation could not complete normally. Catch one only where you can recover, add meaningful context, or translate it; use finally or using to guarantee cleanup; do not use exceptions for expected choices such as ordinary invalid input.

01

Some failures cannot be answered with a normal value

A file operation may fail because the file disappeared, permission was denied, or storage became unavailable. Returning an empty string would confuse a failure with a successfully read empty file. An exception separates the failed control path and carries diagnostic information.

Expected alternatives do not automatically need exceptions. A search that finds no match can return null, and invalid user text can make TryParse return false. Reserve exceptions for operations that cannot fulfill their contract normally.

02

Catch only where you can make a decision

A try block contains work that may fail. catch selects an exception type and handles it. Put more specific catches before broader ones. Catching Exception at every method, printing ‘something went wrong,’ and continuing can hide corruption and erase the evidence needed to fix the cause.

At a user-facing boundary, a catch can translate a technical failure into an actionable message and log the full exception for diagnosis. Deeper code often should let an unexpected exception propagate. If you rethrow the same exception, use throw; rather than throw ex; so the original stack information is preserved.

SituationPreferReason
User types non-numberTryParse branchExpected input outcome
Required file missingCatch FileNotFoundException at boundaryRecovery/message can be specific
Method argument violates contractThrow ArgumentException subtypeCaller supplied invalid state
Unknown internal bugLet propagate to top-level loggingDo not pretend recovery succeeded
03

Throw exceptions that explain the broken contract

A method can use throw when its contract cannot be fulfilled. Prefer standard exception types such as ArgumentNullException, ArgumentOutOfRangeException, and InvalidOperationException when they accurately describe the problem. Include the parameter name and a message that states the requirement.

Custom exception types are useful when callers need to distinguish a domain failure programmatically and standard types do not express it. Do not create a new exception class merely to restate a message.

C#
static decimal Percentage(decimal part, decimal whole)
{
  if (whole == 0m)
      throw new ArgumentOutOfRangeException(
          "whole",
          "Whole must be greater than zero.");

  return part / whole * 100m;
}
04

Cleanup must happen on success and failure

Files, network streams, and some other objects own resources outside managed memory. They implement IDisposable so code can release those resources promptly. A finally block runs whether the try succeeds or fails, but a using statement is the usual clearer tool for a local disposable lifetime.

using translates into cleanup logic that calls Dispose even when an exception interrupts the body. It does not catch the exception; responsibility for cleanup and responsibility for recovery remain separate.

C#
using (StreamReader reader = File.OpenText("notes.txt"))
{
  string firstLine = reader.ReadLine();
  Console.WriteLine(firstLine);
} // reader is disposed here, including on an exception

Good habits

  • Catch the narrowest exception types you can actually handle.
  • Never leave an empty catch block.
  • Use using for disposable resources and keep their scope narrow.
  • Keep detailed exception data out of public responses while retaining it in secure logs.

Quick knowledge check

Answer before you reveal.

01Does a using statement catch exceptions?

No. It guarantees disposal; exceptions still propagate unless a catch handles them.

02Why is catch (Exception) { } dangerous?

It hides every failure, discards diagnostics, and may let the program continue with invalid state.

Practice challenge

Now build it without copying.

Write a method that reads a required integer setting from a small text file. Give separate messages for a missing file and invalid numeric content, and use a using statement when reading with StreamReader.

You are done when

  • Missing and malformed data are not reported as the same problem
  • The reader is disposed on every path
  • Unexpected exceptions are not silently swallowed

Stretch: Move file parsing into a method that returns a result and keep user-facing messages in Main. Explain which layer owns each decision.

Open challenge in playground

Lesson checkpoint

One small step locks it in

Mark this lesson complete, then keep the momentum going.

Complete and continue

Clear up the details

Frequently asked questions

What is the difference between throw and throw ex?

Inside a catch, throw preserves the original stack trace. throw ex starts the trace from the rethrow location and loses useful origin information.

Does .NET garbage collection close files quickly enough?

Do not rely on eventual collection for scarce external resources. Dispose them deterministically with using.

Should exceptions be logged at every layer?

Usually log once at the boundary that handles or terminates the operation. Logging and rethrowing repeatedly creates duplicate noise unless each layer adds distinct useful context.