Checked and Unchecked Contexts in C# Arithmetic
In C#, the checked and unchecked contexts
control how the runtime handles integral arithmetic overflow and
underflow. When arithmetic operations exceed the storage boundaries of a
data type, C# can either throw an exception to prevent silent data
corruption or silently wrap the value around for optimal performance.
This article explains how both contexts function, their syntax as
keywords and statements, the compiler-level defaults governing them, and
best practices for their use.
The Problem: Integer Overflow
Integer types like int, long, and
byte have fixed bit sizes. For example, a standard 32-bit
signed integer (int) has a maximum value of
2,147,483,647 (int.MaxValue). If you add
1 to this value without safeguards, the binary
representation rolls over into negative territory, resulting in
-2,147,483,648.
This rollover behavior can introduce subtle logic bugs, infinite loops, and security vulnerabilities unless handled intentionally.
The checked Context
The checked context enforces strict bounds checking at
runtime. If an integral arithmetic operation (such as addition,
subtraction, multiplication, or conversion) exceeds the minimum or
maximum limit of the target type, an OverflowException is
thrown immediately.
You can apply the checked context as either an
expression or a block statement.
As an Expression
int max = int.MaxValue;
int result = checked(max + 1); // Throws System.OverflowExceptionAs a Block Statement
checked
{
int a = int.MaxValue;
int b = 10;
int c = a + b; // Throws System.OverflowException
}The checked context only applies to integral types
(int, uint, short,
byte, long, etc.) and explicit conversions
between integral types. It does not apply to floating-point types
(float, double), which produce
Infinity or NaN instead of throwing
exceptions.
The unchecked Context
The unchecked context suppresses overflow checking. When
an operation exceeds bounds inside an unchecked context,
the bits are truncated, and the value wraps around without raising any
exceptions.
As an Expression
int max = int.MaxValue;
int result = unchecked(max + 1); // Returns -2,147,483,648As a Block Statement
unchecked
{
int a = int.MaxValue;
int b = 1;
int c = a + b; // Returns -2,147,483,648
}Compiler Defaults and Behavior
C# employs specific defaults depending on whether the code is evaluated at compile time or runtime:
- Runtime Operations: By default, non-constant
arithmetic operations in C# execute within an
uncheckedcontext. This minimizes CPU instruction overhead, prioritizing performance. - Compile-Time Constants: Constant expressions are
evaluated at compile time in a
checkedcontext by default. For example,int x = int.MaxValue + 1;causes a compile-time error (CS0220: The operation overflows at compile time in checked mode) unless explicitly wrapped inunchecked(int.MaxValue + 1). - Project-Wide Configuration: You can change the
runtime default across an entire project by adding
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>to your.csprojfile (equivalent to passing the/checkedflag to the compiler). When enabled, all non-constant arithmetic is treated ascheckedunless explicitly wrapped in anuncheckedblock.
Key Differences
| Feature | checked |
unchecked |
|---|---|---|
| Overflow Behavior | Throws
System.OverflowException |
Truncates bits (wraps around) |
| Default Runtime State | Must be explicitly declared (or enabled via compiler flag) | Default behavior for non-constant code |
| Performance | Slightly slower due to CPU overflow flag checks | Fastest execution (raw CPU instructions) |
| Primary Use Case | Financial, safety, and business-critical math | Bitwise manipulation, hashing, performance loops |
When to Use Each Context
- Use
checkedwhen calculation accuracy is critical to the integrity of the application. This includes calculating monetary values, index bounds, and counters where silent truncation would lead to incorrect business decisions or corrupted persistent data. - Use
uncheckedwhen deliberate overflow is part of the algorithm, such as generating hash codes (e.g., overridingGetHashCode()), cryptography routines, or low-level bit shifting. It is also useful for overriding a project-wide<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>setting in isolated, performance-sensitive algorithms.