Tuesday, January 17, 2023

Checking for Overflow in C#

By default, integral values (such as int, uint, and long) do not throw an exception when they overflow. Instead, they "wrap" -- and this is probably not a value that we want. But we can change this behavior so that an exception is thrown instead.

Short version:

Use a "checked" statement to throw an overflow exception when an integral overflow (or underflow) occurs.

As an alternate, there is also a project property that we can set: CheckForOverflowUnderflow.

Note: This behavior applies to "enum" and "char" which are also integral types in C#.

Let's take a closer look at this.

Default Behavior

Let's start by looking at the default overflow behavior for integer (Int32) -- this applies to other integral types as well.

    int number = int.MaxValue;
    Console.WriteLine($"number = {number}");

    number++;
    Console.WriteLine($"number = {number}");

This creates a new integer variable ("number") and sets its value to the maximum value that a 32-bit integer can hold.

When we increment that value (using the ++ operator), it goes past that maximum value. But instead of throwing an error, the value "wraps" to the lowest integer value.

Here is the output of the above code:

    number = 2147483647
    number = -2147483648

This is probably not the behavior that we want.

Using a "checked" Statement

One way we can fix this is to use a "checked" statement. This consists of the "checked" operator and a code block.

    int number = int.MaxValue;
    Console.WriteLine($"number = {number}");
    checked
    {
        number++;
    }
    Console.WriteLine($"number = {number}");
  

The result of this is that the increment operation is checked to see if it will overflow. If it does, it throws an OverflowException.

Here's the exception if we run this code from Visual Studio:

    System.OverflowException: 'Arithmetic operation resulted in an overflow.'

Let's handle the exception:

    try
    {
        int number = int.MaxValue;
        Console.WriteLine($"number = {number}");

        checked
        {
            number++;
        }
        Console.WriteLine($"number = {number}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"OVERFLOW: {ex.Message}");
    }

Now our output looks like this:

    number = 2147483647
    OVERFLOW: Arithmetic operation resulted in an overflow.

If we overflow an integer value, it probably means that we need to make some changes to the code (such as using a larger type). But the good news is that an overflow exception makes sure that we do not unintentionally use an invalid value.

Using "CheckForOverflowUnderflow"

We may want to apply overflow checks through our entire project. Instead of using individual "checked" statements, we can also set a property on the project: "CheckForOverflowUnderflow".

To see this in action, we will removed the "checked" statement from our code:

    try
    {
        int number = int.MaxValue;
        Console.WriteLine($"number = {number}");

        number++;
        Console.WriteLine($"number = {number}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"OVERFLOW: {ex.Message}");
    }

The code does not throw an exception, and we are back to the wrapped value:

    number = 2147483647
    number = -2147483648

In the project, we can add the "CheckForOverflowUnderflow" property. Here is the project file for our basic console application:

    <Project Sdk="Microsoft.NET.Sdk">

      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <RootNamespace>check_overflow</RootNamespace>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
      </PropertyGroup>

    </Project>

We've set the "CheckForOverflowUnderflow" property to "true". Now when the application runs, the exception is thrown.

    number = 2147483647
    OVERFLOW: Arithmetic operation resulted in an overflow.

"unchecked"

In addition to "checked", there is also an "unchecked" operator. As you might imagine, this does not check for overflow.

So with our project set to "CheckForOverflowUnderflow", we can add an "unchecked" block that will ignore the project setting.

    try
    {
        int number = int.MaxValue;
        Console.WriteLine($"number = {number}");

        unchecked
        {
            number++;
        }
        Console.WriteLine($"number = {number}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"OVERFLOW: {ex.Message}");
    }

The code does not throw an exception, and we are back to the wrapped value:

    number = 2147483647
    number = -2147483648

Wrap Up

Normally, I do not need to worry about overflow or underflow in my code; it's not something that comes up in my applications very often.

One exception is with Fibonacci sequences. I've written a few articles involving Fibonacci sequences (including "Implementing a Fibonacci Sequence with Value Tuples in C# 7" and "Coding Practice: Learning Rust with Fibonacci Numbers"). Since the sequence more or less doubles on each item, it overflows a 32-bit integer very quickly (around the 46th item in the sequence). This is one place where I generally use a larger type (like a long) and also a "checked" statement to make sure I do not end up using invalid values in my code somewhere.

"checked" statements do come with a cost. There is a little overhead that is added in the arithmetic operations. Because of this, I generally leave projects with the default setting ("unchecked" for the project), and then use targeted "checked" statements where I need them.

It's always interesting to find things like this in the C# language. I don't often need it, but it's really good to have it when I do.

Happy Coding!

Monday, January 16, 2023

Full-Day Workshop - Asynchronous & Parallel Programming in C# (March 2023)

I'll be giving a full day workshop (with hands-on labs) at Visual Studio LIVE!, Las Vegas on March 19th, 2023. Now's your chance to spend a full day with me and also learn tons of useful tech during the rest of the week.

Event Info:

Visual Studio LIVE! March 19-24, 2023
Planet Hollywood Resort & Casino
Las Vegas NV
Event Link: https://vslive.com/events/las-vegas-2023/home.aspx

Use the promo code "Clark" to save $500 off the regular price for 4-, 5-, or 6-day packages (Note: you'll need the 6-day package to join me for the full day workshop on Sunday, March 19). Here's a direct link to registration that includes the promo code: http://bit.ly/3HSDHJn

Read on to see what we'll learn in the workshop.

Hands-on Lab: Asynchronous and Parallel Programming in C#

3/19/2023 9:00 a.m. - 6:00 p.m.
Level: Intermediate

Asynchronous programming is a critical skill to take full advantage of today's multi-core systems. But async programming brings its own set of issues. In this workshop, we'll work through some of those issues and get comfortable using parts of the .NET Task Parallel Library (TPL).

We'll start by calling asynchronous methods using the Task Asynchronous Pattern (TAP), including how to handle exceptions and cancellation. With this in hand, we'll look at creating our own asynchronous methods and methods that use asynchronous libraries. Along the way, we'll see how to avoid deadlocks, how to isolate our code for easier async, and why it's important to stay away from "asyc void".

In addition, we'll look at some patterns for running code in parallel, including using Parallel.ForEachAsync, channels, and other techniques. We'll see pros and cons so that we can use the right pattern for a particular problem.

Throughout the day, we'll go hands-on with lab exercises to put these skills into practice.

Objectives:

  • Use asynchronous methods with Task and await 
  • Create asynchronous methods and libraries 
  • How to avoid deadlocks and other pitfalls 
  • Understand different parallel programming techniques

Topics:

Here's a list of some of the topics that we'll cover:

Pre-Requisites:

Basic understanding of C# and object-oriented programming (classes, inheritance, methods, and properties). No prior experience with asynchronous programming is necessary; we'll take care of that as we go.

Attendee Requirements:

  • You must provide your own laptop computer (Windows, Mac, or Linux) for this hands-on lab.
  • All other laptop requirements will be provided to attendees 2 weeks prior to the conference

Hope to See You There!

If you can't make it to this one, keep watching here (and my website: jeremybytes.com) for future public workshops. I'm also available for private workshops for your team - customized to be most relevant to the code that you're building.

Happy Coding!