C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
169: The checked and unchecked Keywords
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
checkedblock to add theamountto the_balance. - Catches the
OverflowExceptionif the balance exceedsint.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.
There are no comments for now.