Record Structs in C#: What They Are and When to Use Them
Introduced in C# 10, record structs combine the memory efficiency of value types with the boilerplate-reducing features of records, such as value-based equality, non-destructive mutation, and built-in formatting. This article explains what record structs are, compares them to traditional structs and record classes, and outlines the specific scenarios where you should use them in your C# applications.
What Is a Record Struct?
A record struct is a value type that automatically
provides built-in functionality for data encapsulation. Unlike standard
classes or standard record classes, record structs are allocated on the
stack (unless boxed or part of a heap-allocated object), eliminating
garbage collection overhead.
Unlike standard struct definitions, declaring a type as
a record struct causes the C# compiler to automatically
generate:
- Value-based equality: Two instances are equal if
they have the same type and store identical values for all properties,
implemented via
IEquatable<T>without the performance cost of reflection. - Built-in formatting: A generated
ToString()method that displays property names and their current values. - Non-destructive mutation: Support for the
withexpression to create copies with modified properties. - Compiler-generated deconstructors: Automatic support for tuple-like deconstruction when using positional syntax.
Syntax Example
You can declare a record struct using positional syntax:
public readonly record struct Point(double X, double Y);Or using standard property definitions:
public record struct GeoLocation
{
public double Latitude { get; init; }
public double Longitude { get; init; }
}Mutable vs. Readonly Record Structs
Unlike record class types—which are immutable by default
when defined with positional syntax—a standard positional
record struct is mutable:
public record struct Dimensions(int Width, int Height);
var box = new Dimensions(10, 20);
box.Width = 15; // Valid: Positional properties in a record struct have get and set accessors.If you require immutability (which is generally recommended for value
types to prevent unexpected side effects), prepend the
readonly modifier:
public readonly record struct Dimensions(int Width, int Height);
var box = new Dimensions(10, 20);
// box.Width = 15; // Compilation error: Properties are init-only.Key Differences: Record Struct vs. Record Class vs. Struct
| Feature | struct |
record struct |
record class |
|---|---|---|---|
| Type | Value type (Stack) | Value type (Stack) | Reference type (Heap) |
| Default Mutability | Configurable | Mutable | Immutable (Positional) |
| Equality Check | Reflection-based | Strongly typed value equality | Strongly typed value equality |
with
Expression |
No | Yes | Yes |
| Inheritance | No | No | Yes |
When Should You Use Record Structs?
You should use record structs when you need a lightweight, transient data structure that prioritizes execution speed and minimizes heap allocations.
1. High-Throughput and Low-Allocation Scenarios
Because record structs are value types, they do not trigger garbage collection passes. They are ideal for high-frequency operations, game loops, financial transactions, and real-time processing pipelines where allocating thousands of reference types per second would degrade performance.
2. Small Data Transfer Objects (DTOs)
When passing small payloads between methods or processing mathematical vectors, 2D/3D coordinates, RGB color codes, or spatial points, record structs provide concise syntax without the overhead of heap allocations.
3. When Value Equality Is Required Without Boilerplate
Standard structs rely on ValueType.Equals, which uses
reflection and boxing, causing significant performance penalties during
comparisons. A record struct automatically implements fast,
strongly typed equality checks out of the box.
4. When Working with
the with Expression
If you need a value type that allows for non-destructive mutation
(creating modified copies while preserving the original), record structs
natively support the with keyword:
var original = new Point(1.0, 2.0);
var modified = original with { Y = 5.0 };When to Avoid Record Structs
Do not use record structs in the following scenarios:
- Large memory footprints: Passing structs larger than 16 bytes across methods causes excessive memory copying, which can hurt performance more than heap allocation.
- Frequent boxing: If the data will be repeatedly
cast to
objector interfaces (such as being stored in non-generic collections), use arecord classinstead. - Inheritance hierarchies: Structs cannot inherit
from other types or serve as base types. If your domain model requires
class inheritance, use a
record class.