Codectionary / Developer documentation / C#

Records

Introduced in C# 9, a record is a reference type designed for immutable data, with built-in value-based equality - two records are considered equal if all their properties match, unlike classes which compare by reference. Declaring a record with positional syntax automatically generates a constructor, init-only properties, plus Equals(), GetHashCode(), ToString(), and a Deconstruct method - all boilerplate that would otherwise be written by hand.

Read the syntax

A positional record declares a primary constructor and generated properties, deconstruction and value-based equality. Unlike an ordinary class, two records with equal component values compare equal.

A with expression creates a copy with specified changes. Record classes are reference types, so this copy is a new object; a record struct has value-type copying rules.

Syntax

public record RecordName(type Prop1, type Prop2);

Examples

Basic Record

A record automatically gets a constructor, properties, value equality, and ToString.

Point p1 = new Point(3, 4);

Console.WriteLine(p1.X);          // 3 - auto-generated property
Console.WriteLine(p1);             // Point { X = 3, Y = 4 } - auto ToString()

Point p2 = new Point(3, 4);
Console.WriteLine(p1 == p2);       // true - value equality, unlike a class
Console.WriteLine(p1.Equals(p2));   // true

public record Point(int X, int Y);
  1. A record automatically gets a constructor, properties, value equality, and ToString.
  2. A positional record declares a primary constructor and generated properties, deconstruction and value-based equality. Unlike an ordinary class, two records with equal component values compare equal.
  3. Point(3, 4) exposes X and a generated string representation. A second Point with the same values compares equal using both == and Equals.

Non-Destructive Mutation with with

The with expression creates a new record with some properties changed, leaving the original untouched.

Point original = new Point(3, 4);
Point moved = original with { X = 10 };

Console.WriteLine(original);  // Point { X = 3, Y = 4 } - unchanged
Console.WriteLine(moved);      // Point { X = 10, Y = 4 }

public record Point(int X, int Y);
  1. The with expression creates a new record with some properties changed, leaving the original untouched.
  2. A positional record declares a primary constructor and generated properties, deconstruction and value-based equality. Unlike an ordinary class, two records with equal component values compare equal.
  3. with copies original and changes only X on the new record. Printing both shows original at (3, 4) and moved at (10, 4).

Record with Additional Members

Records can still define their own methods alongside the automatically generated ones.

Rectangle r = new Rectangle(5, 5);
Console.WriteLine(r.Area);       // 25
Console.WriteLine(r.IsSquare);    // true

public record Rectangle(double Width, double Height)
{
    public double Area => Width * Height;
    public bool IsSquare => Width == Height;
}
  1. Records can still define their own methods alongside the automatically generated ones.
  2. A positional record declares a primary constructor and generated properties, deconstruction and value-based equality. Unlike an ordinary class, two records with equal component values compare equal.
  3. The record also defines computed Area and IsSquare members. A 5-by-5 rectangle reports area 25 and True for the square test.

Deconstruction

Records automatically support deconstruction into separate variables.

Point p = new Point(3, 4);
var (x, y) = p;  // deconstructs automatically

Console.WriteLine($"x={x}, y={y}");

public record Point(int X, int Y);
  1. Records automatically support deconstruction into separate variables.
  2. A positional record declares a primary constructor and generated properties, deconstruction and value-based equality. Unlike an ordinary class, two records with equal component values compare equal.
  3. The generated Deconstruct method places X and Y into x and y. Interpolation prints x=3, y=4.

Best practices

  • Use records for simple, immutable data carriers (DTOs, value objects) instead of hand-writing a full class with Equals, GetHashCode, and ToString
  • Use the with expression to create modified copies instead of mutating a record in place, which keeps the immutability guarantees intact
  • Prefer records over classes whenever value-based equality (comparing by data, not reference) is what you actually want
  • Remember records are reference types stored on the heap like classes - "record" describes their equality/immutability behavior, not their memory location (use record struct for a value-type record)

Runtime & versions

.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

C# language reference

Specifications & further reading