throw and custom exceptions
So far, the alarm was raised by the code of .NET itself: int.Parse, division, an array index. But there is a fire alarm button on every floor, and anyone can press it. Your method can throw an exception too, when it is given data it cannot cope with.
Take a method that withdraws money from an account. What should it do if asked for more than the account holds? Returning a negative balance is wrong. So is returning 0: the calling code would never learn that the money was not withdrawn.
The throw statement
A method can throw an exception itself. To do so, you create an exception object with new and pass it to the throw statement:
Console.WriteLine(Withdraw(100, 30)); // 70
Console.WriteLine(Withdraw(100, 250));
int Withdraw(int balance, int amount)
{
if (amount > balance)
{
throw new ArgumentException("Not enough money.");
}
return balance - amount;
}Let's go through the program step by step:
Withdraw(100, 30)passes the check and returns70.- In the call
Withdraw(100, 250), the conditionamount > balanceis true. new ArgumentException("Not enough money.")creates an exception object with a message.throwthrows it. The method is cut short at once, andreturndoes not run.- Nobody catches the exception, and the program stops with a report.
Unhandled exception. System.ArgumentException: Not enough money.Your exception behaves the same way as the exceptions of .NET: you can catch it with catch (ArgumentException ex), and ex.Message returns the text passed to the constructor.
For your own checks, take a ready-made .NET class if its name fits the error:
ArgumentException: an argument of the method is wrong;ArgumentOutOfRangeException: an argument is outside the allowed bounds;InvalidOperationException: the action cannot be done right now.
Your own exception class
There may be no ready-made class that fits. Say a café has run out of tea. That is not a wrong argument but a situation of its own, and the calling code will need to catch it separately. Then you declare your own exception class, derived from Exception.
try
{
throw new OutOfStockException("No tea left.");
}
catch (OutOfStockException ex)
{
Console.WriteLine("Sorry: " + ex.Message); // Sorry: No tea left.
}
class OutOfStockException : Exception
{
public OutOfStockException(string message) : base(message) { }
}Let's take the example apart:
class OutOfStockException : Exceptiondeclares a new class derived fromException. By convention, its name ends inException.- The constructor takes a message and passes it to the constructor of
Exceptionthrough: base(message). That is whereex.Messagegets its text. throw new OutOfStockException(…)throws an object of the new class, andcatch (OutOfStockException ex)catches exactly that class.
We throw the exception right inside try to keep the example short. In a real program, throw sits in a method, as in Withdraw, and catch in the code that calls that method.
throw; or throw ex;
Sometimes a handler cannot solve the problem completely. For example, it writes the error to a log, while the calling code decides what to do next. Then the exception is thrown on, and this is called rethrowing. For it, you write throw; with no object:
try
{
Console.WriteLine(int.Parse("ten"));
}
catch (FormatException ex)
{
Console.WriteLine("Log: " + ex.Message); // Log: The input string 'ten' was not in a correct format.
throw;
}The program prints the log line, and then the exception goes on. Nobody else catches it, so the program stops with a report about the FormatException.
Interviewers like to ask: how does throw; differ from throw ex;? Both throw the same object, but they treat the path in the report differently. throw; keeps the whole path, while throw ex; starts it over from the line where throw ex; stands. With throw; or throw ex; on line 8, the report of this program keeps these at lines:
| Rethrow | What the at lines show |
|---|---|
throw; | System.Int32.Parse, then line 3, the line with int.Parse |
throw ex; | only line 8, the line with throw ex; itself |
With throw ex;, the place where the error really happened is gone from the report. Compiling such a program gives a warning:
warning CA2200: Re-throwing caught exception changes stack informationThat is why you rethrow with throw;.
Common mistake
A custom exception class was declared without : Exception, and a field for the message was added by hand:
throw new OutOfStockException("No tea left."); // ✗
class OutOfStockException
{
public string Message = "";
public OutOfStockException(string message) => Message = message;
}error CS0029: Cannot implicitly convert type 'OutOfStockException' to 'System.Exception'The program does not compile. Only an object of the class Exception or of a class derived from it can be thrown, and this class has nothing to do with Exception. Add : Exception, remove the Message field and pass the message through : base(message), as in the example above.
Key points
throw new ArgumentException("…")throws an exception: the method is cut short at once, and the exception goes to the calling code.- If a ready-made class fits, use it. If not, declare a class derived from
Exceptionand pass the message through: base(message). throw;incatchthrows the exception on and keeps its path, whilethrow ex;starts the path over from its own line.