Exception Handling
An exception signals that an operation could not complete.
Concept
An exception signals that an operation could not complete. It is an object deriving from System.Exception, carrying a Message, a StackTrace and optionally an InnerException.
Code that may throw catch (SpecificException ex) { } Handle one exception type (most specific first) catch (Exception ex) when (condition)
Exception filter: catch only if the condition is true Always runs — cleanup (also on return or when an exception propagates) throw new X(...) Raise an exception
Rethrow the current exception preserving the stack trace
Rethrows but resets the stack trace to this point (avoid) x ?? throw new...
Propagation: if no catch matches, the exception travels up the call stack; unhandled, it terminates the process with a message and stack trace.
Inner exceptions: wrap low-level errors in domain exceptions while keeping the cause (new EcuException("...", io)).
Custom exceptions: derive from Exception (or a more specific type), end the name with Exception, and add useful properties.
Common types: ArgumentException/ArgumentNullException/ArgumentOutOfRangeException (bad input), InvalidOperationException (wrong state), NotSupportedException, FormatException, IOException, TimeoutException, OperationCanceledException, KeyNotFoundException.
Guard helpers: ArgumentNullException.ThrowIfNull(x), ArgumentException.ThrowIfNullOrWhiteSpace(s), ArgumentOutOfRangeException.ThrowIfNegative(n).
Why It Matters
Robust software — diagnostic tools, services, test frameworks — must fail predictably, report useful information, and clean up hardware sessions, files and connections even when something goes wrong.
Syntax
try { ... }
catch (TimeoutException ex) when (attempt < 3) { ... }
catch (IOException ex) { throw new MyException("context", ex); }
finally { ... }Example
// try / catch / finally, exception filters, custom exceptions, inner exceptions, rethrow
try
{
var tool = new DiagnosticTool();
tool.ReadDtcs("VCU");
tool.ReadDtcs("BMS");
}
catch (EcuTimeoutException ex) when (ex.Retryable) // exception filter
{
Console.WriteLine($"retryable timeout on {ex.Ecu}: {ex.Message}");
}
catch (EcuException ex)
{
Console.WriteLine($"ECU error: {ex.Message}; inner: {ex.InnerException?.GetType().Name}: {ex.InnerException?.Message}");
}
finally
{
Console.WriteLine("finally: session closed");
}
// Order matters: specific first, general last
try
{
int[] data = [1, 2, 3];
Console.WriteLine(data[5]);
}
catch (IndexOutOfRangeException) { Console.WriteLine("index error"); }
catch (Exception ex) { Console.WriteLine($"other: {ex.Message}"); }
// throw; preserves the original stack trace, throw ex; resets it
try { Wrapper(rethrowCorrectly: true); }
catch (InvalidOperationException ex) { Console.WriteLine($"throw; -> origin frame: {FirstFrame(ex)}"); }
try { Wrapper(rethrowCorrectly: false); }
catch (InvalidOperationException ex) { Console.WriteLine($"throw ex; -> origin frame: {FirstFrame(ex)}"); }
// Throw expressions
…Expected Result
With throw; the stack trace still starts in Fail (where the error happened); with throw ex; it starts in Wrapper, and the original location is lost. finally ran before Compute returned its value.
reading DTCs from VCU
reading DTCs from BMS
ECU error: cannot read DTCs from BMS; inner: IOException: CAN transceiver error
finally: session closed
index error
throw; -> origin frame: Program.<<Main>$>g__Fail|0_2
throw ex; -> origin frame: Program.<<Main>$>g__Wrapper|0_3
ArgumentNullException: value
finally ran before returning
Compute() = 1Common Mistakes
An unhandled exception terminates the program — real output (the Average of an empty sequence):
(0xE0434352 is the Windows exit code for an unhandled .NET exception.)
Swallowing exceptions: catch (Exception) { } hides bugs.
throw ex; instead of throw;.
Using exceptions for normal control flow (e.g. int.Parse in a loop instead of TryParse).
Catching Exception too early, deep inside libraries.
Losing the original error when wrapping (forgetting the inner argument).
Throwing from finally (replaces the original exception).
Console.WriteLine("starting");
int[] readings = [];
Console.WriteLine(readings.Average());starting
Unhandled exception. System.InvalidOperationException: Sequence contains no elements
at System.Linq.ThrowHelper.ThrowNoElementsException()
at System.Linq.Enumerable.Average(IEnumerable`1 source)
at Program.<Main>$(String[] args) in m20_unhandled.cs:line 3
[process exited with code -532462766 (0xE0434352)]Best Practices
Catch only exceptions you can handle; let others propagate to a top-level handler that logs them.
Throw the most specific built-in type that fits; create custom exceptions for domain failures.
Validate arguments at public entry points with the ThrowIf… helpers.
Use finally or using (Module 30) for cleanup.
Include context in messages ("ECU BMS did not respond within 2000 ms"), never secrets.
Interview Relevance
Very high: "throw vs throw ex", "what is finally for?", "exception filters", "checked vs unchecked exceptions (C# has none)", "custom exceptions", "should you catch Exception?".
Practice Questions
Write ReadConfig(path) that wraps FileNotFoundException in a ConfigurationException with context.
Retry an operation on TimeoutException up to three times using an exception filter.
Show that finally runs when a return is inside try.
Create DtcNotFoundException with a Code property.
Convert a method that returns -1 on error to one that throws, and one that uses the TryXxx pattern. Which is better and when?
Quick Quiz
Q1. (Multiple choice) Which rethrow preserves the original stack trace? A) throw ex; B) throw; C) throw new Exception(ex.Message); D) return ex; Answer: B (verified).
Q2. (True/False) A finally block runs even if the try block returns.
Answer: True (verified).
Q3. (Predict the output) new int[0].Average() in a program without try.
Answer: The program terminates with an unhandled InvalidOperationException: Sequence contains no elements (verified).
Q4. (Find the bug) catch (Exception) { } catch (IOException) { }
Answer: The general catch comes first, so the specific one is unreachable — the compiler reports error CS0160; order catches from specific to general.
Coding Challenge
Write T WithRetry<T>(Func<T> action, int attempts) that retries only on TimeoutException, rethrows the last timeout after the final attempt, and prints each failed attempt.
int calls = 0;
string result = WithRetry(() =>
{
calls++;
if (calls < 3) throw new TimeoutException($"attempt {calls} timed out");
return "ECU responded";
}, attempts: 5);
Console.WriteLine($"{result} after {calls} calls");
static T WithRetry<T>(Func<T> action, int attempts)
{
for (int i = 1; ; i++)
{
try { return action(); }
catch (TimeoutException ex) when (i < attempts)
{
Console.WriteLine($"retrying: {ex.Message}");
}
}
}