Many of the other answers are justifying the approach of an existing exception mechanism. That's fine, but it can also be illuminating to compare exceptions to other approaches and ideas.
In particular, consider the stack trace attached to an exception. We can generate a stack trace at any point in a program, and it's common to think of them as the history of the computation (i.e. "how did the program reach this point?"), but that's not quite right. A stack trace only tells us about calls which have not yet returned, i.e. those waiting for the current step to finish. Hence it can be more illuminating to think of a stack trace as the future of a computation (i.e. "what will resume after this step returns?").
In your example, the stack will look something like the following, where each line is a stack frame ('_' indicates where to 'plug in' the return value of the line above):
assertNonZero(_) // I've made up the name of this step, for illustration
Notice that the multiplication, addition and subtraction doesn't appear, since they've already finished. The call stack shows us what remains to happen in the future, which is also called a continuation. The above stack is waiting to perform a non-zero check on the divisor, then perform a division with that checked divisor, then perform a string conversion of that result, and finally show a message box with that string.
Throwing an exception will bypass the usual return mechanism (instead, the stack will be popped until we find a matching
catch handler). In this example, the
DivideByZeroException is thrown by the non-zero check, once it's called with the value
0. Hence one reason the exception doesn't contain the dividend (the number
1 in this case) is that the exception is thrown before we reached the division step (where the
As an extreme example, let's imagine we alter your example a little:
MessageBox.Show(("The result is: " + (1/(a*a-(a+a))).ToString()));
This involves another piece of data, the string
"The result is: ". If we want the
DivideByZeroException to contain the dividend of the division step (which would have happened, if the exception were not thrown), do we also want it to contain that string (since we would have prepended it, if the exception were not thrown)? That seems less reasonable, but it highlights the fact they're both just data for steps that the exception prevented from happening.
Note that the stack trace does contain a frame for the division; and the altered version would also contain a frame for the string append. Hence we could argue that including the dividend in a
DivideByZeroException is a poor solution, since it's an ad-hoc decision, it requires co-ordination with the exception's consumers (via the public interface of
DivideByZeroException), and we must face similar decisions every time we implement a new exception type. A more elegant solution would be to stop excluding arguments from the stack trace: that solves the problem for all exceptions, and gives callers a unified interface to access any data (via the frames of the stack trace).
The reason argument data is excluded from stack frames is probably a mixture of performance and encapsulation, and may vary between languages. Interestingly, if arguments were accessible from stack traces, we could implement resumable exceptions, e.g. our exception handler could replace the
0 in the above stack trace with a different number, and calculate what would have happened in that case.
Resumable exceptions aren't a common language feature; however, they are closely related to coroutines, which are becoming popular. In particular,
throw is similar to
catch are similar to
await, and all of these can be generalised to
reset (the latter implementing "delimited continuations")