Skip to Content
Course content

169: The checked and unchecked Keywords

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably taken for granted that adding one to a number always makes it larger. In a math class, that's a universal truth. But in software engineering, we're dealing with fixed-width integers. If you keep adding to an int, you eventually hit the ceiling—int.MaxValue—and then something strange happens. C# doesn't stop you, and it doesn't warn you. It just wraps the value around to the lowest possible negative number.

I've seen this cause some truly nightmare-inducing bugs in production, usually in systems calculating totals or offsets. The program doesn't crash; it just starts producing mathematically impossible results, and you spend three days hunting for a bug that is essentially "the number got too big."

The Danger of Silent Wrapping

The naive approach is to just perform your arithmetic and assume the hardware is handling it. Look at this snippet:

int currentPoints = int.MaxValue; 
currentPoints += 1; 
Console.WriteLine(currentPoints); // Output: -2147483648

To the CPU, this is perfectly normal. It's just binary flipping. But to your user, their high score just plummeted from 2.1 billion to negative 2.1 billion. The "wrong" way here isn't necessarily the code itself, but the assumption that the operation is safe. By default, C# performs arithmetic in an unchecked context for the sake of raw performance. Checking for overflows takes a few extra CPU cycles, and for 99% of operations, those cycles aren't worth the cost.

Forcing an Exception with Checked

When you're dealing with critical data—financial transactions, security offsets, or anything where a wrong number is worse than a crash—you should use the checked keyword. This tells the runtime, "If this operation overflows, I want you to throw an OverflowException immediately."

You can wrap a whole block of code or just a single expression. Here is how I'd handle a critical point addition:

try 
{
    checked 
    {
        int currentPoints = int.MaxValue;
        currentPoints += 1; // This now throws OverflowException
    }
}
catch (OverflowException ex)
{
    Console.WriteLine("Point limit reached! We can't add more.");
}

The trade-off here is a tiny bit of performance for a massive gain in reliability. In my experience, the "cost" of the checked keyword is negligible compared to the cost of debugging a silent data corruption issue in a live database. If you're not sure if a value could overflow, but you know that an overflow would be catastrophic, wrap it in a checked block.

When Wrapping is Actually the Goal

Now, you might wonder why we even have an unchecked keyword if that's the default behavior. There are two main reasons. First, some developers enable "Check for arithmetic overflow" in their project-wide compiler settings. If that's turned on, every single addition in the project will throw an exception on overflow.

Second, there are specific algorithms—like calculating checksums or creating hash codes—where the "wrap-around" behavior is actually the intended logic. In those cases, you use unchecked to explicitly tell anyone reading your code, "I know this looks like it might overflow, and I'm doing it on purpose."

// Explicitly allowing wrap-around for a simple hash
unchecked 
{
    int hash = 17;
    hash = hash * 31 + inputString.GetHashCode();
}

It's a signal of intent. It transforms a "potential bug" into a "documented feature."




📋 Practical Task

The Overflow-Proof Wallet Guard

You are building a digital wallet system. The wallet uses an int to store the balance in cents. To prevent catastrophic errors where a user's balance wraps around to a negative number after a massive deposit, you need to implement a safe addition method.

Create a class called Wallet with a private integer field _balance. Implement a method public void Deposit(int amount) that does the following:

  • Uses a checked block to add the amount to the _balance.
  • Catches the OverflowException if the balance exceeds int.MaxValue.
  • If an overflow occurs, prints "Transaction failed: Balance overflow" to the console and ensures the balance remains at its previous value.

Test your code by initializing a wallet with int.MaxValue - 10 and attempting to deposit 20 cents.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.