13.1. Error Handling and Validation#
Programs fail in predictable ways: bad input, missing files, unexpected values, and invalid assumptions. Good software does not pretend errors never happen — it handles them deliberately.
13.1.1. Validation vs. Exceptions#
Validation checks expected bad input before doing risky work.
Exceptions handle unexpected failures at runtime.
Validation should be your first line of defense. Exceptions are a fallback for when something still goes wrong.
13.1.2. Basic try / catch / finally#
Use finally for cleanup (closing resources, resetting state):
try
{
Console.Write("Enter a number: ");
int value = int.Parse(Console.ReadLine()!);
Console.WriteLine($"You entered {value}");
}
catch (FormatException)
{
Console.WriteLine("Input must be a whole number.");
}
catch (OverflowException)
{
Console.WriteLine("Number is outside the valid int range.");
}
finally
{
Console.WriteLine("Input attempt complete.");
}
13.1.3. Prefer TryParse for User Input#
If invalid input is expected, avoid throwing exceptions entirely:
Console.Write("Enter age: ");
string raw = Console.ReadLine() ?? "";
if (int.TryParse(raw, out int age) && age >= 0)
{
Console.WriteLine($"Age recorded: {age}");
}
else
{
Console.WriteLine("Please enter a non-negative whole number.");
}
Guideline:
Use
TryParsefor normal input flow where bad input is common.Use
try/catcharound operations that can fail unpredictably (file I/O, network, external dependencies).
13.1.4. File I/O with Safe Handling#
Catch specific exception types before broad ones:
string path = "scores.txt";
try
{
using StreamReader reader = new StreamReader(path);
string? line;
while ((line = reader.ReadLine()) != null)
Console.WriteLine(line);
}
catch (FileNotFoundException)
{
Console.WriteLine($"Could not find file: {path}");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Permission denied while reading the file.");
}
catch (IOException ex)
{
Console.WriteLine($"I/O error: {ex.Message}");
}
13.1.5. Throwing Exceptions Intentionally#
Throw when an API is used incorrectly and execution should stop:
static double Divide(double numerator, double denominator)
{
if (denominator == 0)
throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
return numerator / denominator;
}
Console.WriteLine(Divide(10, 2)); // 5
Console.WriteLine(Divide(10, 0)); // throws
This is better than returning meaningless sentinel values that hide bugs.
13.1.6. Error Message Quality#
Bad |
Better |
|---|---|
|
|
Good error messages are: specific, user-actionable, and free of raw internal noise.
13.1.7. Common Mistakes#
Catching
Exceptioneverywhere and ignoring detailsSwallowing exceptions without logging or reporting
Using exceptions for normal control flow (use
TryParseinstead)Showing stack traces directly to end users
13.1.8. Practice#
Read a value from the console and keep prompting until a valid
doubleis entered.Open a file path provided by the user; print a friendly message for: file not found, access denied, and unknown I/O error.
Add argument checks to one of your existing methods and throw
ArgumentExceptionwhen input is invalid.
Footnotes