Records are the idiomatic way to model DTOs, messages and value objects with almost no code.
A record is a class (or struct) designed for data. The positional form declares everything in one line:
public record Person(string Name, int Age);The compiler generates init-only properties, a constructor, Deconstruct, a readable ToString() (Person { Name = Ada, Age = 36 }), and value equality: two records are equal when their type and all properties are equal, so == compares contents rather than references.
Records encourage immutability. To "change" one, create a modified copy with a with-expression: var older = p with { Age = 37 };. The original is untouched. The copy is shallow: a List property is shared between both.
Variants:
record(orrecord class) is a reference type.record structis a value type with the same conveniences;readonly record structmakes it immutable.- You can add methods, extra properties and validation in a body, and records support inheritance from other records.
Because equality uses all properties, records work well as dictionary keys and in sets, and make tests easy to write (Assert.Equal(expected, actual)).
Use records for DTOs, API request/response models, events and value objects. Use regular classes for entities with identity and mutable lifecycle, such as EF Core entities.
Example
var a = new Person("Ada", 36);
var b = new Person("Ada", 36);
Console.WriteLine(a == b); // True (value equality)
Console.WriteLine(ReferenceEquals(a, b)); // False
Console.WriteLine(a); // Person { Name = Ada, Age = 36 }
var older = a with { Age = 37 };
Console.WriteLine($"{a.Age} {older.Age}"); // 36 37
var (name, age) = older;
Console.WriteLine($"{name} is {age}"); // Ada is 37
var seen = new HashSet<Point> { new(1, 2), new(1, 2) };
Console.WriteLine(seen.Count); // 1
record Person(string Name, int Age);
readonly record struct Point(int X, int Y);Remember
- Positional records generate properties, constructor, ToString and Deconstruct
- Records compare by value, classes by reference
- with creates a modified shallow copy
- record struct gives the same conveniences as a value type
Watch out for
- Assuming with deep-copies collections inside the record; it only copies references.