Variables and Types — C#’s Type System

C# is a statically-typed language — every variable has a type fixed at compile time, and the compiler rejects type mismatches before the program ever runs. This catches an entire class of bugs that dynamic languages only discover at runtime. C# has two fundamental categories of types: value types (stored directly on the stack, copied on assignment) and reference types (allocated on the heap, assigned by reference). Understanding this distinction is essential to writing correct C# across every part of this series.

Built-In Value Types

// ── Integer types ────────────────────────────────────────────────────────────
int    age    = 30;           // 32-bit signed — most common whole-number type
long   views  = 9_000_000L;  // 64-bit signed — L suffix marks the literal as long
short  s      = 32_767;       // 16-bit signed (rarely needed)
byte   b      = 255;          // 8-bit unsigned: 0–255 (e.g. RGB colour channels)

// ── Floating-point types ──────────────────────────────────────────────────────
double pi     = 3.14159265358979;  // 64-bit — DEFAULT for decimal literals
float  temp   = 98.6f;             // 32-bit — f suffix is REQUIRED
decimal price = 19.99m;            // 128-bit exact decimal — USE FOR CURRENCY

// ── Other value types ─────────────────────────────────────────────────────────
bool   isActive = true;
char   grade    = 'A';        // single Unicode character (single quotes)

// ── Implicit typing with var — type still fixed at compile time ───────────────
var count   = 0;              // inferred as int
var message = "Hello";        // inferred as string
var total   = 100.0m;         // inferred as decimal
Note: Always use decimal for monetary values — never double or float. Binary floating-point types cannot represent many decimal fractions exactly: 0.1 + 0.2 as a double yields 0.30000000000000004. The decimal type uses base-10 arithmetic and represents values like 0.1 exactly, which is essential for price calculations, tax rates, and financial reports. This is one of the most common bugs in enterprise applications. The m suffix is required: decimal price = 19.99m — without it the compiler sees a double literal and refuses the assignment.
Tip: Use var when the type is obvious from context — var users = new List<User>() is perfectly clear. Avoid it when the right-hand side does not reveal the type: var result = GetData() forces the reader to look up the return type. In ASP.NET Core controller methods and EF Core queries, var is idiomatic because the types are obvious from the method being called. Visual Studio and VS Code both show the inferred type on hover.
Warning: Numeric literals have default types: integer literals like 42 are int; decimal literals like 3.14 are double. Suffixes override this: 42L is long, 3.14f is float, 3.14m is decimal. Forgetting the m suffix causes a compile error: decimal price = 19.99 fails because 19.99 is a double literal and cannot be implicitly converted to decimal. This error appears frequently when developers first work with the decimal type.

Reference Types and Nullable

// Reference types are allocated on the heap; variables hold a reference (pointer)
string  name   = "Alice";           // immutable reference type
int[]   scores = { 95, 87, 92 };    // array on the heap

// ── Nullable value types (Nullable<T> shorthand: T?) ─────────────────────────
int?    optionalAge = null;          // can hold an int value or null
double? rating      = null;

if (optionalAge.HasValue)
    Console.WriteLine($"Age: {optionalAge.Value}");
Console.WriteLine($"Age: {optionalAge ?? 0}");  // 0 if null

// ── Null operators ────────────────────────────────────────────────────────────
string? middleName = null;
string  display    = middleName ?? "N/A";        // ?? returns right side if left is null
int?    len        = middleName?.Length;         // ?. returns null if left side is null
middleName        ??= "Unknown";                 // ??= assigns only if currently null

// ── Nullable reference types (enabled by <Nullable>enable in .csproj) ─────────
string  required = "must have a value";  // non-nullable — compiler warns on null
string? optional = null;                 // explicitly nullable — must check before use

// ── Type conversion ────────────────────────────────────────────────────────────
int    i    = 42;
double d    = i;              // implicit widening — safe, no data loss
int    back = (int)d;         // explicit narrowing cast — may truncate
string text = i.ToString();   // every type supports ToString()
int    parsed  = int.Parse("42");                          // throws on invalid
bool   ok      = int.TryParse("abc", out int result);      // returns false, result = 0

Value vs Reference — Assignment Behaviour

// Value types are COPIED — independent after assignment
int a = 10;
int b = a;    // b is a separate copy
b = 20;
Console.WriteLine(a);   // 10 — a is unchanged

// Reference types SHARE the same heap object after assignment
int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1;       // both point to the SAME array
arr2[0] = 99;
Console.WriteLine(arr1[0]);  // 99 — arr1 sees the change!

// string is a reference type but IMMUTABLE — effectively behaves like a value type
string s1 = "hello";
string s2 = s1;
s2 = "world";           // creates a NEW string object — s1 is unchanged
Console.WriteLine(s1);  // "hello"

Quick Reference

Type Category Use For
int Value / 32-bit Counts, IDs, general whole numbers
long Value / 64-bit Large whole numbers, file sizes
decimal Value / 128-bit Currency and financial values
double Value / 64-bit Scientific floats — NOT money
bool Value True/false flags
string Reference (immutable) All text
T[] Reference (fixed size) Typed fixed-size collections
int? Nullable value Optional numbers, nullable DB columns

Common Mistakes

Mistake 1 — Using double for currency

❌ Wrong:

double subtotal = 10.10 + 9.90;
Console.WriteLine(subtotal);   // 20.000000000000004 — floating-point imprecision!

✅ Correct:

decimal subtotal = 10.10m + 9.90m;
Console.WriteLine(subtotal);   // 20.00 — exact

Mistake 2 — NullReferenceException on nullable strings

❌ Wrong:

string? name = null;
Console.WriteLine(name.Length);   // NullReferenceException at runtime!

✅ Correct:

Console.WriteLine(name?.Length ?? 0);   // null-safe: prints 0

🧠 Test Yourself

Given: int a = 5; int b = a; b = 10; — what is a? And: int[] x = {1,2}; int[] y = x; y[0] = 99; — what is x[0]?