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 with expression 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 object or interfaces (such as being stored in non-generic collections), use a record class instead.
  • Inheritance hierarchies: Structs cannot inherit from other types or serve as base types. If your domain model requires class inheritance, use a record class.