Structs, Enums & Record Structs Masterclass
Welcome to Phase 7 (Chapter 19): C# Structs, Enums & Records Masterclass! Choosing the right data structure directly impacts application memory consumption and performance. In this chapter, we master struct value-type behavior, enum strongly typed enumerations, record classes, C# 10 record struct, value-based equality, immutable data modeling, and guidelines for choosing between class, struct, or record.
A struct is a light-weight Value Type stored directly on the Stack (or inside its containing object). Unlike classes, assigning a struct variable to another variable copies the entire data byte-by-byte rather than copying a heap memory reference pointer.
| Property | Class (Reference Type) | Struct (Value Type) | Record (Reference / Value) |
|---|---|---|---|
| Memory Location | Heap (Pointer on Stack) | Stack (Direct data allocation) | Heap (Record Class) / Stack (Record Struct) |
| Assignment Behavior | Reference Copy (Shares object) | Value Copy (Independent clone) | Value Copy or Reference Copy |
| Equality Check | Reference Equality (by default) | Value Equality (compares fields) | Automatic Value Equality |
| Inheritance | Supports Class Inheritance | No Struct Inheritance (Interfaces only) | Record Class inheritance supported |
| Best Used For | Complex entities with state & logic | Small lightweight value containers (<16 bytes) | Immutable Data Transfer Objects (DTOs) |
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString() => $"Point({X}, {Y})";
}
// Value Type Copy Behavior Demonstration
Point p1 = new Point(10, 20);
Point p2 = p1; // COPIES ACTUAL VALUES (Independent object on Stack!)
p2.X = 99;
Console.WriteLine($"p1: {p1}"); // Point(10, 20) โ p1 remains unchanged!
Console.WriteLine($"p2: {p2}"); // Point(99, 20)
An enum (enumeration) defines a strongly typed set of named integer constants, eliminating magic numbers and magic strings across your codebase.
enum OrderStatus
{
Pending = 1,
Processing,
Shipped,
Delivered,
Cancelled
}
OrderStatus status = OrderStatus.Processing;
// Enum with Switch Expression
string message = status switch
{
OrderStatus.Pending => "Order received and waiting for payment.",
OrderStatus.Processing => "Order is being packed in the warehouse.",
OrderStatus.Shipped => "Order is out for delivery with courier.",
OrderStatus.Delivered => "Order delivered successfully!",
_ => "Order status unknown."
};
Console.WriteLine($"Status: {status} (Code: {(int)status}) -> {message}");
C# 9+ introduced Records to model immutable data with positional parameters, non-destructive mutation (the with expression), and automatic value-based equality.
// 1. Record Class (Reference Type with Value Equality)
public record Product(int Id, string Name, decimal Price);
// 2. Record Struct (Value Type with Record features โ C# 10+)
public readonly record struct GeoLocation(double Latitude, double Longitude);
Product prod1 = new(101, "Laptop", 75000.00m);
Product prod2 = new(101, "Laptop", 75000.00m);
// Value-based equality check
Console.WriteLine($"prod1 == prod2: {prod1 == prod2}"); // True!
// Non-destructive mutation using 'with'
Product updatedProd = prod1 with { Price = 69999.00m };
Console.WriteLine($"Updated Product: {updatedProd}");
Q1: When should I choose a Struct over a Class?
Choose a struct when data size is small (<16 bytes), objects are short-lived, immutable, and created frequently inside loops to reduce Garbage Collector allocation pressure.
Q2: What is the underlying type of an enum?
By default, an enum's underlying type is int (32-bit integer). You can override this to byte, short, or long (e.g., enum Status : byte).