Structs, records and value vs reference types
The two kinds of types in C#, when to write a struct, and how records give you value equality and immutability for free.
int and double are value types; string, arrays and classes are reference types. This lesson shows how to create your own value types (structs) and the modern records.
Value types vs reference types
| Value type | Reference type | |
|---|---|---|
int, double, bool, char, DateTime, enum, your structs | string, arrays, List<T>, your classes and records | |
int?) | ||
null |
Structs
public struct Point3{ public double X { get; } public double Y { get; } public double Z { get; } public Point3(double x, double y, double z) { X = x; Y = y; Z = z; } public double DistanceTo(Point3 o) => Math.Sqrt((X - o.X) * (X - o.X) + (Y - o.Y) * (Y - o.Y) + (Z - o.Z) * (Z - o.Z)); public override string ToString() => $"({X}, {Y}, {Z})";} var a = new Point3(0, 0, 0);var b = new Point3(3, 4, 12);Console.WriteLine($"{a} → {b}: {a.DistanceTo(b)}");(0, 0, 0) → (3, 4, 12): 13
Keep structs small (roughly 16 bytes or less is the usual guideline) and preferably immutable ( readonly struct).Structs can’t inherit from other structs or classes, but they can implement interfaces. Use a class when the data is large, needs identity (“this exact beam”), or must be shared and changed in one place.
Records: data classes in one line
public record Section(string Name, double DepthMm, double KgPerMetre); var s1 = new Section("IPE300", 300, 42.2);var s2 = new Section("IPE300", 300, 42.2); Console.WriteLine(s1); // readable ToString for freeConsole.WriteLine(s1 == s2); // value equality: same data → equalvar heavier = s1 with { KgPerMetre = 49.1 }; // copy with one changeConsole.WriteLine(heavier.KgPerMetre);Section { Name = IPE300, DepthMm = 300, KgPerMetre = 42.2 }
True
49.1init) properties, Equals/== based on the values, GetHashCode, a nice ToString and deconstruction. Records are ideal for data that is passed around and compared: DTOs, settings, messages, results.
Choosing
| Write a… | When |
|---|---|
class | |
record | |
struct / readonly record struct |
record Money(decimal Amount, string Currency) and a method Money Add(Money a, Money b) that throws InvalidOperationException if the currencies differ. Add 1500 INR and 750 INR, print the result, and check equality with a new Money(2250, "INR").
Show the solution
public record Money(decimal Amount, string Currency); static Money Add(Money a, Money b){ if (a.Currency != b.Currency) throw new InvalidOperationException("Currencies differ."); return a with { Amount = a.Amount + b.Amount };} var total = Add(new Money(1500, "INR"), new Money(750, "INR"));Console.WriteLine(total);Console.WriteLine(total == new Money(2250, "INR"));Money { Amount = 2250, Currency = INR }
TrueRemember
Value types hold their data and are copied on assignment; reference types share one object. Use small, immutable structs for simple values like points and vectors. Records give value equality, ToString,withcopies and immutability for free.class = identity and behaviour; record = data; struct = tiny values.