Type System

C# is strongly typed. Every variable has a type known at compile time.

Value Types

Stored directly on the stack. Copying creates an independent copy.

int count = 42;
double price = 9.99;
bool isActive = true;
char letter = 'A';
decimal money = 100.50m;  // high precision for financial

Structs

Custom value types:

public struct Point
{
    public double X { get; init; }
    public double Y { get; init; }
}

var p = new Point { X = 1.0, Y = 2.0 };

Enums

public enum Status { Active, Inactive, Pending }

var s = Status.Active;

Reference Types

Stored on the heap. Variables hold references, not the data itself.

string name = "Alice";       // string is a reference type
int[] numbers = [1, 2, 3];   // collection expression (.NET 8+)
object anything = 42;         // boxing: value type -> object

Nullable Reference Types

C# 8+ has nullable reference types (NRT). Enabled by default in .NET 6+.

string name = "Alice";   // non-null by default
string? maybe = null;     // explicitly nullable

// Compiler warns on potential null dereference
int len = maybe.Length;   // Warning: maybe may be null
int len = maybe?.Length ?? 0;  // Safe: null-conditional + null-coalescing

Gotcha: NRT is a compile-time feature only. It doesn’t prevent null at runtime. Libraries without NRT annotations can still pass null to your code.

Pattern Matching

Modern C# has powerful pattern matching:

string Describe(object obj) => obj switch
{
    int n when n > 0 => "positive integer",
    int n when n < 0 => "negative integer",
    int => "zero",
    string s => $"string of length {s.Length}",
    null => "null",
    _ => "something else"
};

Records

Immutable data types with value equality (.NET 5+):

public record User(string Name, string Email);

var alice = new User("Alice", "alice@example.com");
var clone = alice with { Email = "new@example.com" };

// Value equality (not reference equality)
var same = new User("Alice", "alice@example.com");
Console.WriteLine(alice == same); // True

Tip: Use record for DTOs, API responses, and any data that shouldn’t be mutated after creation. Use record struct for small value types that need value equality.