14.2. Record Types#
You have been writing record since Chapter 9 as a shorthand for simple data types.
Here we look at what record actually provides and when to prefer it over a class.
14.2.1. What record Gives You#
A record is a reference type (like a class) with four things added automatically:
Value-based equality — two records are equal if all their properties are equal
ToString()— prints all property values readablyDeconstruction — unpack into variables like a tuple
withexpressions — create a copy with selected properties changed
14.2.1.1. Value-Based Equality#
record Point(int X, int Y);
var a = new Point(1, 2);
var b = new Point(1, 2);
var c = new Point(3, 4);
Console.WriteLine(a == b); // True — same values
Console.WriteLine(a == c); // False
Console.WriteLine(a); // Point { X = 1, Y = 2 }
14.2.1.2. with Expressions#
Create a modified copy without mutating the original.
record Person(string Name, int Age);
var alice = new Person("Alice", 30);
var olderAlice = alice with { Age = 31 };
Console.WriteLine(alice); // Person { Name = Alice, Age = 30 }
Console.WriteLine(olderAlice); // Person { Name = Alice, Age = 31 }
Console.WriteLine(alice == olderAlice); // False
14.2.1.3. Deconstruction#
record Point(int X, int Y);
var p = new Point(3, 7);
var (x, y) = p;
Console.WriteLine($"x={x}, y={y}"); // x=3, y=7
14.2.2. Positional vs. Standard Records#
// Positional — compact; properties are init-only
record Point(int X, int Y);
// Standard — more control over property visibility/mutability
record Person
{
public string Name { get; init; } = "";
public int Age { get; set; }
}
Use positional for immutable value-like data (coordinates, domain events, DTO return values). Use standard when some properties need to be mutable.
14.2.3. Init-Only Properties#
init is like set, but only callable during object construction. It makes individual properties settable at creation time while remaining immutable afterward — without needing a positional record.
record Person
{
public string Name { get; init; } = "";
public int Age { get; init; }
}
This is what the compiler generates automatically for positional record properties.
record Person
{
public string Name { get; init; } = "";
public int Age { get; init; }
}
var p = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p); // Person { Name = Alice, Age = 30 }
// p.Age = 31; // compile error — init-only after construction
// Works fine in object initializer:
var q = p with { Age = 31 }; // creates a copy
Console.WriteLine(q); // Person { Name = Alice, Age = 31 }
Init-only properties are also common in regular classes when you want immutable-after-creation semantics without making the whole type a record:
class Config
{
public string Host { get; init; } = "localhost";
public int Port { get; init; } = 8080;
}
14.2.4. record struct#
record struct gives the same conveniences on a value type — allocated on the stack, copied on assignment.
record struct Size(int Width, int Height);
Prefer record struct for tiny, frequently-created data (e.g., 2D coordinates in a game loop). Prefer record (class) for everything else.
14.2.5. record vs. class vs. struct#
|
|
|
|
|---|---|---|---|
Equality |
Reference |
Value |
Value |
|
✗ |
✓ |
✓ |
Auto |
✗ |
✓ |
✓ |
Heap allocated |
✓ |
✓ |
✗ |
Inheritance |
✓ |
✓ |
✗ |
Use record when your type is primarily data — no complex behaviour, identity does not matter, immutability is preferred.
14.2.6. Practice#
Define
record Temperature(double Celsius)and add a computed propertyFahrenheit. Print two equal temperatures and verify==istrue.Using
with, create a sequence: start withPerson("Bob", 20), then produce versions at age 21, 22, 23 without mutation.Define
record struct Vector2(float X, float Y)and write anAddmethod that returns a newVector2.
Footnotes