Understanding arithmetic overflow checking in C#

Default context = unchecked

By default, arith­metic oper­a­tions and con­ver­sions in C# are exe­cut­ed in an unchecked con­text. This means that for a signed inte­ger it over­flows from int.MaxValue to int.MinValue and under­flows from int.MinValue to int.MaxValue, hence both state­ments below eval­u­ates to true:

(int.MinValue – 1) == int.MaxValue;
(int.MaxValue + 1) == int.MinValue;

Sim­i­lar­ly, for an unsigned inte­ger it will under­flow from 0 to uint.MaxValue and over­flow from uint.MaxValue to 0:

(0 – 1) == uint.MaxValue; // uint.MinValue = 0
(uint.MaxValue + 1) == 0;

This also applies to con­ver­sions too:

(int)((long) int.MaxValue + 1) == int.MinValue; // true

Pitfalls

This default behav­iour of swal­low­ing the excep­tions seems rather strange and unex­pect­ed to me, giv­en that when an over­flow hap­pens it’s usu­al­ly of inter­est to me as a devel­op­er to know about it and deal with it as appro­pri­ate because:

  • if an over­flow is to hap­pen dur­ing nor­mal usage of the appli­ca­tion it prob­a­bly means there’s some­thing wrong with the design/assumptions of the appli­ca­toin
  • if an over­flow is to hap­pen and the appli­ca­tion is allowed to con­tin­ue and per­sist the overflowed/underflowed val­ue it could mean impor­tant pieces of data are left in an invalid state which is difficult/impossible to revert
  • the jump is large! Imag­ine see­ing your account bal­ance go from £2147483647 to £–2147483648 after cred­it­ing £1 into it..

With that said, unchecked con­text per­forms sig­nif­i­cant­ly bet­ter than checked imple­men­ta­tion, which is prob­a­bly why it was cho­sen as the default.

Also, you will prob­a­bly want to use unchecked blocks for cal­cu­lat­ing hash codes where it’s the bit pat­terns that mat­ters not the mag­ni­tude of the inte­ger val­ue, i.e.:

public class MyClass
{
    …
    public override int GetHashCode()
    {
        unchecked
        {
            return ….
        }
    }
}

Scope

There are two things to keep in mind when using checked/unchecked blocks:

They’re always LOCAL to the method

Which means if you call anoth­er method from with­in a checked block the method will still exe­cute in the default con­text (unchecked):

checked
{
    // this method will still execute in unchecked context
    DoSomethingThatOverflows();
}
...
public void DoSomethingThatOverflows()
{
    // no overflow exception is thrown...
    var overflowed = int.MaxValue + 1;
}

The con­text a line of code exe­cutes in is deter­mined by the MOST inner checked/unchecked state­ment block

Hence the fol­low­ing code will exe­cute in unchecked con­text:

checked
{
    unchecked
    {
        var over = int.MaxValue + 1;
    }
}

Project-wide arithmetic overflow/underflow checks

If you require arith­metic overflow/underflow check­ing on a project-wide scale, there is a prop­er­ty you can set from with­in Visu­al Stu­dio. Go to project prop­er­ties and find the Build tab, click “Advanced…” and tick the “Check for arith­metic overflow/underflow” box. But remem­ber, you would prob­a­bly still want to make sure GetH­ash­Code is exe­cut­ed in an unchecked con­text.