dotnet Exception Handling

.NET Exceptions – System.DivideByZeroException

Moving along through our in-depth .NET Exception Handling series, today we face off against the System.DivideByZeroException. The System.DivideByZeroException is thrown when attempting to divide an integer or a decimal by zero.

Normally divide by zero errors are pretty boring, but in this article we’ll examine the System.DivideByZeroException in more detail and see how .NET converts your written code into low-level human-readable instructions, a handful of which can bubble up to a DivideByZeroException. We’ll look at some functional code samples in both C# and the Common Intermediate Language (CIL) that is executed behind the scenes, so let’s get going!

The Technical Rundown

All .NET exceptions are derived classes of the System.Exception base class, or derived from another inherited class therein. The full exception hierarchy of this error is:

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

When Should You Use It?

The System.DivideByZeroException is not a particularly exciting or difficult exception to understand, considering the massive quantity of unique exceptions the .NET Framework provides. However, a discussion of exactly how the System.DivideByZeroException works and is thrown, behind the scenes, leads to some interesting territory. As mentioned in the introduction, the manner in which .NET compiles and processes the high-level code you write (in C# or otherwise) is by converting it into machine-readable instructions known as the Command Intermediate Language or CIL. This CIL is then converted to bytecode, from which a Common Language Infrastructure assembly is created, which can then be directly executed by the computer.

To understand what all this means it’s much easier to look at some code examples. We’ll start with our C# code, which defines a couple constants (Numerator and Denominator), then performs a handful of tests by trying to divide by zero using int, double, and decimal value types. For example, here we have the series of instructions for performing a division by zero using doubles:

The TestToDouble() method houses the try-catch block, so that each unique data type test can throw an exception separately, without hindering execution of other tests. Executing the double divide by zero test works just fine, without throwing any exceptions. Instead, as we can see in the output, since we’re using floating-point numbers in this case, the result is a value of positive infinity:

However, let’s see what happens when we use int and decimal values instead:

In both cases, attempts to divide by zero result in a System.DivideByZeroException. To see exactly why int and decimal attempts throw an exception, but double does not, we need to dig into the CIL code that is produced when compiling our C# code above. Below we see the full Airbrake.DivideByZeroException namespace C# code converted into CIL:

Note: To view the CIL of your own .NET code, check out a reflection tool such as ILSpy.

As you can see, CIL is a stack-based assembly language, interpreting instructions in an ordered fashion. There’s far too much to go over everything here, so we’ll instead focus on a few snippets of CIL that are pertinent to our System.DivideByZeroException example.

Here we have the DivideInt() method, which accepts two int parameters and performs division of a over b, then returns the result as int:

Let’s break down what’s happening here. The method signature with the int32 parameters a and b is easy to parse, since it’s similar to how normal .NET code looks and behaves. The .maxstack property indicates how many items are required within the stack to fully execute this particular method. In this case, we only need to reserve two items in the stack.

.locals specifies what value type local variables that aren’t declared a specific type should be set to. In this case, int32 is the only type we’re using, so that’s the default.

Now we get into the actual instruction set of the method. All of these instructions begin with an opcode, which essentially defines what instruction to perform.

  • nop is a placeholder instruction.
  • ldarg.0 loads the first argument onto the evaluation stack.
  • ldarg.1 loads the second argument onto the evaluation stack.
  • div is the bread and butter of this method. It divides the previous two values on the evaluation stack (which we just loaded), and adds the resulting floating-point or integer value onto the stack.
  • stloc.0 extracts the current value from the top of the stack (in this case, the result of our division) and stores it in the first local variable index.
  • br.s (unconditionally) transfers control to the specified instruction (IL_0007 in this case). This is similar to jmp or goto instructions found in other assembly languages.
  • ldloc.0 loads the first local variable, which we stored two instructions ago using stloc.0. This causes the result of our division to be loaded to the top of the stack.
  • Finally, ret returns from the current method and pushes the value at the top of this method’s stack to the calling method’s evaluation stack.

So, why is it important to know how the CIL works when calling these division methods? Because, only a select handful of CIL opcodes are actually capable of producing a System.DivideByZeroException. As you might have guessed, one such CIL opcode is div, which was part of the fundamental instruction we saw in the CIL of the DivideInt() method above. In fact, the way the CIL works is that if the second value in the evaluation stack (the one we loaded using ldarg.1 above) is equal to zero, a System.DivideByZeroException is thrown.

To get the most out of your own applications and to fully manage any and all .NET Exceptions, check out the Airbrake .NET Bug Handler, offering real-time alerts and instantaneous insight into what went wrong with your .NET code, along with built-in support for a variety of popular development integrations including: JIRA, GitHub, Bitbucket, and much more.