.NET C# Integer Types – And a Tricky Puzzle to Master Them


.NET C# Integer Types – And a Tricky Puzzle to Master Them

:brain: Introduction

In .NET C#, numbers aren’t just numbers. Choosing the correct integer type is essential for writing safe, efficient, and bug-free code. Whether you’re working on finance calculations, embedded systems, or everyday logic, understanding how different integer types work — and how they interact — is crucial.

This article covers:

  • The different integer types in C#
  • Their ranges and signed/unsigned nature
  • Common pitfalls (like overflows)
  • A fun puzzle to test your skills

:1234: Integer Types in C#

C# provides both signed and unsigned integer types, with varying sizes. Here’s a summary:

Type Size Signed Range
byte 8-bit :cross_mark: No 0 to 255
sbyte 8-bit :white_check_mark: Yes -128 to 127
short 16-bit :white_check_mark: Yes -32,768 to 32,767
ushort 16-bit :cross_mark: No 0 to 65,535
int 32-bit :white_check_mark: Yes -2,147,483,648 to 2,147,483,647
uint 32-bit :cross_mark: No 0 to 4,294,967,295
long 64-bit :white_check_mark: Yes −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
ulong 64-bit :cross_mark: No 0 to 18,446,744,073,709,551,615

:puzzle_piece: Common Pitfalls to Watch For

1. Overflow & Underflow

byte b = 255;
b++;  // Becomes 0 due to overflow (wraps around)

2. Implicit vs. Explicit Casting

int a = 1000;
byte b = (byte)a;  // b = 1000 % 256 = 232 (data loss)

3. Signed vs. Unsigned Comparisons

int x = -1;
uint y = 1;

Console.WriteLine(x < y);  // True
Console.WriteLine((uint)x < y);  // False! (because (uint)-1 is 4294967295)

:exploding_head: The Tricky Puzzle

Let’s test your integer knowledge!

Puzzle: What will this print?

using System;

class Program
{
    static void Main()
    {
        byte a = 250;
        sbyte b = -10;
        int c = a + b;

        Console.WriteLine($"a + b = {c}");

        byte d = (byte)(a + b);
        Console.WriteLine($"(byte)(a + b) = {d}");

        unchecked
        {
            byte e = (byte)(a + 10);
            Console.WriteLine($"unchecked byte: {e}");
        }

        checked
        {
            try
            {
                byte f = checked((byte)(a + 10));
                Console.WriteLine($"checked byte: {f}");
            }
            catch (OverflowException)
            {
                Console.WriteLine("Overflow detected!");
            }
        }
    }
}

:puzzle_piece: Puzzle Explanation

Let’s break it down:

Line 1:

byte a = 250;
sbyte b = -10;
  • a is 250 (byte), b is -10 (sbyte)

Line 2:

int c = a + b;
  • Implicitly promoted to int, so: 250 + (-10) = 240

Output: a + b = 240


Line 3:

byte d = (byte)(a + b); // (byte)240 => 240 (within range)

Output: (byte)(a + b) = 240


Line 4 (unchecked context):

byte e = (byte)(a + 10);  // 250 + 10 = 260 => wraps to 260 % 256 = 4

Output: unchecked byte: 4


Line 5 (checked context):

byte f = checked((byte)(a + 10));  // OverflowException at runtime

Output: Overflow detected!


:white_check_mark: Key Takeaways

  • Integer types differ in size, range, and signed-ness — understand these before choosing a type.
  • Use checked and unchecked to control overflow behavior.
  • Be cautious when mixing types (e.g., byte + sbyte, or int with uint).
  • Use explicit casting carefully to avoid unexpected data loss.

:test_tube: Try It Yourself

Modify the values in the puzzle above and see how behavior changes:

  • What if a = 255 and b = 1?
  • What if you remove checked?
  • Try assigning int values to ushort or sbyte directly.

:chequered_flag: Conclusion

Integer types may seem simple, but their nuances can lead to unexpected behavior — especially in high-performance or low-level logic. Mastering them early helps you write safer, more robust .NET applications.