- Tell a compile error apart from an exception at run time
- Catch exceptions with
try, severalcatchblocks andfinally - Throw an exception with
throwand create your own exception class - Choose approaches such as
TryParsefor expected mistakes
The compiler finds many mistakes before a program starts, but not all of them. A user types “abc” instead of an age, a file has been deleted, the internet drops, someone divides by zero — these problems only show up while the program is running. In C#, such situations raise an exception. If you do not catch it, the program stops with an “Unhandled exception” message; if you do, you can explain the problem to the user and carry on.
try and catch
Code that might fail goes into a try block. As soon as an exception occurs, the rest of the try block is skipped and control jumps to a matching catch block. Each catch handles a particular exception type, so you can react differently to different errors.
string[] inputs = ["42", "abc", "99999999999"];
foreach (string input in inputs)
{
try
{
int number = int.Parse(input);
Console.WriteLine($"OK: {number}");
}
catch (FormatException)
{
Console.WriteLine($"'{input}' is not a number");
}
catch (OverflowException)
{
Console.WriteLine($"'{input}' is too big for int");
}
}OK: 42 'abc' is not a number '99999999999' is too big for int
An exception is also an object: in catch (IndexOutOfRangeException ex), the variable ex holds its details, for example ex.Message — the explanation text. All exceptions derive from the Exception class, so catch (Exception ex) catches any error — always put it last. A finally block runs every time, whether there was an error or not: it is the right place to close a file or free a resource.
int[] scores = [90, 75, 88];
try
{
Console.WriteLine(scores[5]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
Console.WriteLine("This line always runs.");
}
try
{
int zero = 0;
Console.WriteLine(10 / zero);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}Error: Index was outside the bounds of the array. This line always runs. DivideByZeroException: Attempted to divide by zero.
| Exception | When it happens |
|---|---|
FormatException | int.Parse("abc") — the text is not a number |
DivideByZeroException | a whole number is divided by zero |
IndexOutOfRangeException | an index that does not exist in the array |
KeyNotFoundException | a key that is not in the dictionary |
NullReferenceException | using a member of a variable that is null |
FileNotFoundException | the file you try to open does not exist |
throw: raising an exception yourself
If a method cannot do its job properly, it should throw an exception with throw instead of quietly returning a wrong result. There are many ready-made types: ArgumentException for a bad argument, InvalidOperationException for an operation not allowed in the object's current state. If needed, you can create your own exception class that derives from Exception — a real use of the inheritance from the previous lesson.
var wallet = new Wallet(100);
try
{
wallet.Pay(30);
wallet.Pay(250);
wallet.Pay(10);
}
catch (NotEnoughMoneyException ex)
{
Console.WriteLine($"Payment failed: {ex.Message}");
}
Console.WriteLine($"Left: {wallet.Balance} AZN");
class NotEnoughMoneyException(string message) : Exception(message);
class Wallet(decimal balance)
{
public decimal Balance { get; private set; } = balance;
public void Pay(decimal amount)
{
if (amount > Balance)
throw new NotEnoughMoneyException($"need {amount} AZN, have {Balance} AZN");
Balance -= amount;
Console.WriteLine($"Paid {amount} AZN");
}
}Paid 30 AZN Payment failed: need 250 AZN, have 70 AZN Left: 70 AZN
wallet.Pay(10) never ran: after an exception, the rest of the try block is skipped.When not to use exceptions
Exceptions are for unexpected situations. A user typing something wrong is completely expected, so to check text, int.TryParse is better than try/catch: the code is shorter, runs faster and shows the intention clearly. The same rule applies to dictionaries: TryGetValue checks for a missing key without any error.
string[] answers = ["15", "fifteen", "-3"];
foreach (string answer in answers)
{
if (!int.TryParse(answer, out int age))
Console.WriteLine($"'{answer}': please type digits");
else if (age is < 0 or > 120)
Console.WriteLine($"'{answer}': age must be 0-120");
else
Console.WriteLine($"Age accepted: {age}");
}Age accepted: 15 'fifteen': please type digits '-3': age must be 0-120
Key points
- An exception is an error that happens while the program runs; if it is not caught, the program stops.
- When an exception occurs in a
tryblock, the remaining lines are skipped and the matchingcatchruns. - Specific
catchblocks come first andcatch (Exception)last;finallyalways runs. throw new ...Exception("...")reports a bad situation; derive your own exceptions fromException.- For expected mistakes,
TryParseandTryGetValueare better than exceptions.
Check yourself
10 questions. Every correct answer earns XP.
int.Parse("abc") throw?