.NET Exceptions – System.OverflowException

Moving along through our detailed .NET Exception Handling series, today we’ll be tackling the ever-popular System.OverflowException. With a name that most developers will probably recognize, the System.OverflowException in .NET indicates that an invalid arithmetic, casting, or conversion error occurred within a checked context.

Throughout this article we’ll explore the System.OverflowException in greater detail, starting with a look at the (rather simple) .NET exception hierarchy chain into which it falls. We’ll also go over a few functional C# code samples that will illustrate the two different scenarios in which System.OverflowExceptions are commonly thrown, so you’re better able to handle them in your own code. Let’s get going!

The Technical Rundown

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?

To understand what the System.OverflowException is, we should first briefly examine what overflow errors are in a more general sense. There are a few different types of overflow in computing.

As you may recall, we explored the notion of stack overflow in our .NET Exception Handling – System.StackOverflowException article. Feel free to check that out for all the details, but the long and short of it is that a stack overflow occurs when an application attempts to utilize more memory than was allocated to it in the memory address space. This results in a stack overflow exception in most applications, because the system recognizes the application is attempting to use memory that doesn’t “belong” to it.

As in the case of the System.OverflowException, a general overflow typically refers to an integer overflow. This is similar to a stack overflow, except that, instead of attempting to use memory outside of the allowable bounds, the application is attempting to create numeric values outside the allowable bounds. We explored this concept a bit in our Ruby Exception Handling: NoMemoryError article. The basic issue is that any given data type, such as an integer, has a maximum value based on the number of bits (or bytes) it is allocated. When your application attempts to create a value that would exceed that number of bits to store, unless it can be automatically converted to a larger data type, an integer overflow is thrown.

To see this in action we’ve created two methods for testing. The first of these methods is MultiplyNumbers(int a, int b, bool @checked = true):

Nothing too fancy going on here, except we’re using the @checked boolean parameter to determine if we should use a checked or unchecked context during the calculation. In C#, a checked context forces the common language runtime (CLR) to raise System.OverflowExceptions when overflows occur, while unchecked context simply ignores them and truncates the resulting value.

To test out our MultiplyNumbers(int a, int b, bool @checked = true) method we are making two calls to it:

If you aren’t familiar, the underscores (_) in our large number literal are a new feature added in C# 7.0 called digit separators, which allow us to visually change how numbers are displayed without impacting their underlying value. Feel free to read more about this and other C# 7.0 features in our What’s New in C# 7.0? – Digit Separators, Reference Returns, and Binary Literals article.

Anyway, as you can see we’re merely attempting to multiple one billion by three, which should result in a value of three billion. Executing these calls gives us the following output:

As we can see, the first checked context call throws a System.OverflowException because the total value of three billion exceeds the maximum value that can be stored in an integer of 2,147,483,647. On the other hand, the second unchecked context call doesn’t throw an error, and instead produces an interesting result of -1,294,967,296. Why on earth are we getting a negative result when multiplying two positive numbers together? The answer lies in how the computer handles overflow truncation.

To examine this, what what happens if we take the -1,294,967,296 result we got, then subtract int.MinValue and add int.MaxValue to it:

That’s incredibly close to our expected result of three billion. In fact, the loss of one extra digit is due to the fact that signed number values must represent the number zero with a particular combination of bits. This “extra” value that is taken up during overflow rollover/truncation is lost in our resulting number above, giving us one less than the expected value when added back together.

Cool. Now let’s take a look at the ConvertValueToType<T>(int value, bool @checked = true) method:

This one is a bit more complex because we’re allowing the method to be called with a generic type (T). This method attempts to convert the passed value into whatever type T was provided. We can use this to make some type conversions in our calling code:

Just as before, we’re making two calls to our method, the first being checked and the second unchecked context. Our goal here is to convert the int value of 200 into a sbyte, which is stored as a signed 8-bit integer in the background. Calling this code produces the following output:

The first checked context call, as expected, throws a System.OverflowException because we’re attempting to convert a value of 200 into a signed 8-bit integer, which can only handle a maximum positive value of 127. However, our unchecked context call is also throwing a System.OverflowException for the same reason, even though it shouldn’t produce overflow errors. The reason can be gleaned from the stack trace that produced the error, which I’ve left in the output above. Since our method makes use of the Convert.ChangeType() method to perform the actual conversion, that built-in .NET method is overriding our unchecked context setting and enabling a checked context within its own execution.

To remedy this we can try explicitly converting our int value to an sbyte:

Sure enough, running this code results in a properly converted value, as expected:

As before, the result is negative because we’ve “rolled over” the maximum positive value of 127, just as we did above with our integer multiplication attempts.

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.