Records
Records (C# 9.0+) are a special kind of class (a reference type), or of struct (record struct in C# 10.0+), designed mainly to encapsulate immutable data. They give a concise syntax for defining properties, constructors and deconstructors. Records automatically get compiler-generated implementations of value-based equality (overriding Equals, GetHashCode, == and !=), ToString() and deconstruction.
Line 1 declares the record. Line 8 prints it, line 9 shows two records with the same values being equal, and line 10 shows two with different values not being equal.
public record Person(string FirstName, string LastName, int Age); // Usage:// var person1 = new Person("John", "Doe", 30);// var person2 = new Person("John", "Doe", 30);// var person3 = new Person("Jane", "Doe", 28); // Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe, Age = 30 }// Console.WriteLine(person1 == person2); // True (value-based equality)// Console.WriteLine(person1 == person3); // Falsewith expressions
Records support non-destructive mutation through with expressions. A with expression creates a new record instance by copying an existing one and modifying the specified properties.
var olderJohn = person1 with { Age = 31 };Console.WriteLine(olderJohn); // Person { FirstName = John, LastName = Doe, Age = 31 }Console.WriteLine(person1 == olderJohn); // FalseRecord versus class
A record is a specialized class, unless it is declared as a record struct. A class centers on object identity and mutable state. A record centers on immutable data and value equality. The compiler generates the differences:
| Class | Record | |
|---|---|---|
| Equality | By reference: two instances with the same values are not equal | By content: two instances with the same values are equal |
| Properties | Mutable (get; set;) by default |
Init-only (get; init;) in the positional form |
| Copying | Manual, field by field | with copies and changes chosen values |
ToString() |
The type name, such as MyApp.User |
The properties, such as User { FirstName = Jane, LastName = Doe } |
Record class and record struct
A plain record is a record class: a reference typeValue types and reference typesA value type variable holds the value. A reference type variable holds a reference to an object.Opens in a new tab, since C# 9. Since C# 10 you can write record struct for a value type that gets the same value equality, ToString and with. A positional record struct has mutable properties unless you write readonly record struct, which is the form to prefer.
Either way, two records with the same values are equal. For a class, that is the difference from the default, which compares references, so two separate objects with the same values are not equal. Both forms, and how the properties are declared, are in the () and noteRecords: the () way and the {} wayA record can be declared with its data in parentheses or in a body.Opens in a new tab. The type comparison is in class versus structClass versus structA struct is a value type and a class is a reference type.Opens in a new tab.
public record Person(string Name); // a record class: the same as "record class"public record class Employee(string Name); // a reference type, spelled outpublic record struct Point(int X, int Y); // a value type, with mutable propertiespublic readonly record struct Coordinates(double Lat, double Lon); // a value type, immutableWhere it matters
Records are excellent for Data Transfer Objects (DTOs), for immutable state in applications, and for any case where you want value-like semantics (comparison by value rather than by reference) for what are structurally classes. They significantly reduce boilerplate code. Init-only settersInit-only settersA property you can set while creating the object, and never again.Opens in a new tab work well with them.
Sources
- milanm/csharp-cheatsheet, GitHub · accessed May 16, 2025